Correct issue where messages were over-trimmed
[quassel.git] / src / core / coreuserinputhandler.cpp
index 33d1f67..bc9fac9 100644 (file)
 #  include "cipher.h"
 #endif
 
+#if QT_VERSION < 0x050000
+// QChar::LineFeed is Qt 5
+static const QChar QCharLF = QChar('\n');
+#else
+static const QChar QCharLF = QChar::LineFeed;
+#endif
+
 CoreUserInputHandler::CoreUserInputHandler(CoreNetwork *parent)
     : CoreBasicHandler(parent)
 {
@@ -228,7 +235,7 @@ void CoreUserInputHandler::doMode(const BufferInfo &bufferInfo, const QChar& add
     if (!isNumber || maxModes == 0) maxModes = 1;
 
     QStringList nickList;
-    if (nicks == "*") { // All users in channel
+    if (nicks == "*" && bufferInfo.type() == BufferInfo::ChannelBuffer) { // All users in channel
         const QList<IrcUser*> users = network()->ircChannel(bufferInfo.bufferName())->ircUsers();
         foreach(IrcUser *user, users) {
             if ((addOrRemove == '+' && !network()->ircChannel(bufferInfo.bufferName())->userModes(user).contains(mode))
@@ -437,8 +444,18 @@ void CoreUserInputHandler::handleMe(const BufferInfo &bufferInfo, const QString
     if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages())
         return;  // server buffer
     // FIXME make this a proper event
-    coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(), "ACTION", msg);
-    emit displayMsg(Message::Action, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
+
+    // Split apart messages at line feeds.  The IRC protocol uses those to separate commands, so
+    // they need to be split into multiple messages.
+    QStringList messages = msg.split(QCharLF);
+
+    foreach (auto message, messages) {
+        // Handle each separated message independently
+        coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(),
+                                                          "ACTION", message);
+        emit displayMsg(Message::Action, bufferInfo.type(), bufferInfo.bufferName(), message,
+                        network()->myNick(), Message::Self);
+    }
 }
 
 
@@ -473,12 +490,16 @@ void CoreUserInputHandler::handleMsg(const BufferInfo &bufferInfo, const QString
         return;
 
     QString target = msg.section(' ', 0, 0);
-    QByteArray encMsg = userEncode(target, msg.section(' ', 1));
+    QString msgSection = msg.section(' ', 1);
+
+    std::function<QByteArray(const QString &, const QString &)> encodeFunc = [this] (const QString &target, const QString &message) -> QByteArray {
+        return userEncode(target, message);
+    };
 
 #ifdef HAVE_QCA2
-    putPrivmsg(serverEncode(target), encMsg, network()->cipher(target));
+    putPrivmsg(target, msgSection, encodeFunc, network()->cipher(target));
 #else
-    putPrivmsg(serverEncode(target), encMsg);
+    putPrivmsg(target, msgSection, encodeFunc);
 #endif
 }
 
@@ -494,11 +515,19 @@ void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QStrin
 void CoreUserInputHandler::handleNotice(const BufferInfo &bufferInfo, const QString &msg)
 {
     QString bufferName = msg.section(' ', 0, 0);
-    QString payload = msg.section(' ', 1);
     QList<QByteArray> params;
-    params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), payload);
-    emit putCmd("NOTICE", params);
-    emit displayMsg(Message::Notice, typeByTarget(bufferName), bufferName, payload, network()->myNick(), Message::Self);
+    // Split apart messages at line feeds.  The IRC protocol uses those to separate commands, so
+    // they need to be split into multiple messages.
+    QStringList messages = msg.section(' ', 1).split(QCharLF);
+
+    foreach (auto message, messages) {
+        // Handle each separated message independently
+        params.clear();
+        params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), message);
+        emit putCmd("NOTICE", params);
+        emit displayMsg(Message::Notice, typeByTarget(bufferName), bufferName, message,
+                        network()->myNick(), Message::Self);
+    }
 }
 
 
@@ -541,7 +570,8 @@ void CoreUserInputHandler::handlePing(const BufferInfo &bufferInfo, const QStrin
     if (param.isEmpty())
         param = QTime::currentTime().toString("hh:mm:ss.zzz");
 
-    putCmd("PING", serverEncode(param));
+    // Take priority so this won't get stuck behind other queued messages.
+    putCmd("PING", serverEncode(param), QByteArray(), true);
 }
 
 
@@ -560,12 +590,24 @@ void CoreUserInputHandler::handleQuery(const BufferInfo &bufferInfo, const QStri
 {
     Q_UNUSED(bufferInfo)
     QString target = msg.section(' ', 0, 0);
-    QString message = msg.section(' ', 1);
-    if (message.isEmpty())
-        emit displayMsg(Message::Server, BufferInfo::QueryBuffer, target, tr("Starting query with %1").arg(target), network()->myNick(), Message::Self);
-    else
-        emit displayMsg(Message::Plain, BufferInfo::QueryBuffer, target, message, network()->myNick(), Message::Self);
-    handleMsg(bufferInfo, msg);
+    // Split apart messages at line feeds.  The IRC protocol uses those to separate commands, so
+    // they need to be split into multiple messages.
+    QStringList messages = msg.section(' ', 1).split(QCharLF);
+
+    foreach (auto message, messages) {
+        // Handle each separated message independently
+        if (message.isEmpty()) {
+            emit displayMsg(Message::Server, BufferInfo::QueryBuffer, target,
+                            tr("Starting query with %1").arg(target), network()->myNick(),
+                            Message::Self);
+            // handleMsg is a no-op if message is empty
+        } else {
+            emit displayMsg(Message::Plain, BufferInfo::QueryBuffer, target, message,
+                            network()->myNick(), Message::Self);
+            // handleMsg needs the target specified at the beginning of the message
+            handleMsg(bufferInfo, target + " " + message);
+        }
+    }
 }
 
 
@@ -576,9 +618,10 @@ void CoreUserInputHandler::handleQuit(const BufferInfo &bufferInfo, const QStrin
 }
 
 
-void CoreUserInputHandler::issueQuit(const QString &reason)
+void CoreUserInputHandler::issueQuit(const QString &reason, bool forceImmediate)
 {
-    emit putCmd("QUIT", serverEncode(reason));
+    // If needing an immediate QUIT (e.g. core shutdown), prepend this to the queue
+    emit putCmd("QUIT", serverEncode(reason), QByteArray(), forceImmediate);
 }
 
 
@@ -594,13 +637,25 @@ void CoreUserInputHandler::handleSay(const BufferInfo &bufferInfo, const QString
     if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages())
         return;  // server buffer
 
-    QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg);
+    std::function<QByteArray(const QString &, const QString &)> encodeFunc = [this] (const QString &target, const QString &message) -> QByteArray {
+        return channelEncode(target, message);
+    };
+
+    // Split apart messages at line feeds.  The IRC protocol uses those to separate commands, so
+    // they need to be split into multiple messages.
+    QStringList messages = msg.split(QCharLF, QString::SkipEmptyParts);
+
+    foreach (auto message, messages) {
+        // Handle each separated message independently
 #ifdef HAVE_QCA2
-    putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, network()->cipher(bufferInfo.bufferName()));
+        putPrivmsg(bufferInfo.bufferName(), message, encodeFunc,
+                   network()->cipher(bufferInfo.bufferName()));
 #else
-    putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg);
+        putPrivmsg(bufferInfo.bufferName(), message, encodeFunc);
 #endif
-    emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
+        emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), message,
+                        network()->myNick(), Message::Self);
+    }
 }
 
 
@@ -763,56 +818,24 @@ void CoreUserInputHandler::defaultHandler(QString cmd, const BufferInfo &bufferI
 }
 
 
-void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher)
+void CoreUserInputHandler::putPrivmsg(const QString &target, const QString &message, std::function<QByteArray(const QString &, const QString &)> encodeFunc, Cipher *cipher)
 {
-    // Encrypted messages need special care. There's no clear relation between cleartext and encrypted message length,
-    // so we can't just compute the maxSplitPos. Instead, we need to loop through the splitpoints until the crypted
-    // version is short enough...
-    // TODO: check out how the various possible encryption methods behave length-wise and make
-    //       this clean by predicting the length of the crypted msg.
-    //       For example, blowfish-ebc seems to create 8-char chunks.
+    Q_UNUSED(cipher);
+    QString cmd("PRIVMSG");
+    QByteArray targetEnc = serverEncode(target);
 
-    static const char *cmd = "PRIVMSG";
-    static const char *splitter = " .,-!?";
+    std::function<QList<QByteArray>(QString &)> cmdGenerator = [&] (QString &splitMsg) -> QList<QByteArray> {
+        QByteArray splitMsgEnc = encodeFunc(target, splitMsg);
 
-    int maxSplitPos = message.count();
-    int splitPos = maxSplitPos;
-    forever {
-        QByteArray crypted = message.left(splitPos);
-        bool isEncrypted = false;
 #ifdef HAVE_QCA2
-        if (cipher && !cipher->key().isEmpty() && !message.isEmpty()) {
-            isEncrypted = cipher->encrypt(crypted);
+        if (cipher && !cipher->key().isEmpty() && !splitMsg.isEmpty()) {
+            cipher->encrypt(splitMsgEnc);
         }
 #endif
-        int overrun = lastParamOverrun(cmd, QList<QByteArray>() << target << crypted);
-        if (overrun) {
-            // In case this is not an encrypted msg, we can just cut off at the end
-            if (!isEncrypted)
-                maxSplitPos = message.count() - overrun;
-
-            splitPos = -1;
-            for (const char *splitChar = splitter; *splitChar != 0; splitChar++) {
-                splitPos = qMax(splitPos, message.lastIndexOf(*splitChar, maxSplitPos) + 1); // keep split char on old line
-            }
-            if (splitPos <= 0 || splitPos > maxSplitPos)
-                splitPos = maxSplitPos;
+        return QList<QByteArray>() << targetEnc << splitMsgEnc;
+    };
 
-            maxSplitPos = splitPos - 1;
-            if (maxSplitPos <= 0) { // this should never happen, but who knows...
-                qWarning() << tr("[Error] Could not encrypt your message: %1").arg(message.data());
-                return;
-            }
-            continue; // we never come back here for !encrypted!
-        }
-
-        // now we have found a valid splitpos (or didn't need to split to begin with)
-        putCmd(cmd, QList<QByteArray>() << target << crypted);
-        if (splitPos < message.count())
-            putPrivmsg(target, message.mid(splitPos), cipher);
-
-        return;
-    }
+    putCmd(cmd, network()->splitMessage(cmd, message, cmdGenerator));
 }