Rework message splitting to properly handle encrypted messages
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 5 Aug 2010 09:05:42 +0000 (11:05 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Thu, 5 Aug 2010 09:05:42 +0000 (11:05 +0200)
Since cutting off a crypted message produces garbage, and we can't reliably predict
how long the encrypted version will be in relation to the cleartext string, we need
to do some extra trickery in the message splitting code.

As a side effect, the split char now also remains on the previous line, which is
aesthetically way more pleasing than starting a line with a space or punctuation.
trickery for this case. Since

src/core/coreuserinputhandler.cpp
src/core/coreuserinputhandler.h

index 185baf7..4336865 100644 (file)
@@ -344,10 +344,10 @@ void CoreUserInputHandler::handleMsg(const BufferInfo &bufferInfo, const QString
   QByteArray encMsg = userEncode(target, msg.section(' ', 1));
 
 #ifdef HAVE_QCA2
   QByteArray encMsg = userEncode(target, msg.section(' ', 1));
 
 #ifdef HAVE_QCA2
-  encMsg = encrypt(target, encMsg);
+  putPrivmsg(serverEncode(target), encMsg, network()->cipher(target));
+#else
+  putPrivmsg(serverEncode(target), encMsg);
 #endif
 #endif
-
-  putPrivmsg(serverEncode(target), encMsg, false);
 }
 
 void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QString &msg) {
 }
 
 void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QString &msg) {
@@ -440,10 +440,10 @@ void CoreUserInputHandler::handleSay(const BufferInfo &bufferInfo, const QString
 
   QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg);
 #ifdef HAVE_QCA2
 
   QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg);
 #ifdef HAVE_QCA2
-  encMsg = encrypt(bufferInfo.bufferName(), encMsg);
+  putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, network()->cipher(bufferInfo.bufferName()));
+#else
+  putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg);
 #endif
 #endif
-
-  putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, false);
   emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
 }
 
   emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
 }
 
@@ -548,28 +548,54 @@ void CoreUserInputHandler::defaultHandler(QString cmd, const BufferInfo &bufferI
   emit putCmd(serverEncode(cmd.toUpper()), serverEncode(msg.split(" ")));
 }
 
   emit putCmd(serverEncode(cmd.toUpper()), serverEncode(msg.split(" ")));
 }
 
-// TODO: handle cutoff of encrypted messages
-void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, bool encrypted) {
-
-  QByteArray temp = message;
+void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, 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.
 
   static const char *cmd = "PRIVMSG";
 
   static const char *cmd = "PRIVMSG";
-  int overrun = lastParamOverrun(cmd, QList<QByteArray>() << target << temp);
-  if(overrun) {
-    static const char *splitter = " .,-";
-    int maxSplitPos = temp.count() - overrun;
-    int splitPos = -1;
-    for(const char *splitChar = splitter; *splitChar != 0; splitChar++) {
-      splitPos = qMax(splitPos, temp.lastIndexOf(*splitChar, maxSplitPos));
+  static const char *splitter = " .,-";
+
+  int maxSplitPos = message.count();
+  int splitPos = maxSplitPos;
+  forever {
+    QByteArray crypted = message.left(splitPos);
+    bool isEncrypted = false;
+#ifdef HAVE_QCA2
+    if(cipher && !message.isEmpty()) {
+      isEncrypted = cipher->encrypt(crypted);
     }
     }
-    if(splitPos <= 0) {
-      splitPos = maxSplitPos;
+#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;
+
+      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!
     }
     }
-    putCmd(cmd, QList<QByteArray>() << target << temp.left(splitPos));
-    putPrivmsg(target, temp.mid(splitPos), 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;
     return;
-  } else {
-    putCmd(cmd, QList<QByteArray>() << target << temp);
   }
 }
 
   }
 }
 
@@ -603,7 +629,10 @@ int CoreUserInputHandler::lastParamOverrun(const QString &cmd, const QList<QByte
 }
 
 #ifdef HAVE_QCA2
 }
 
 #ifdef HAVE_QCA2
-QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray &message_) const {
+QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray &message_, bool *didEncrypt) const {
+  if(didEncrypt)
+    *didEncrypt = false;
+
   if(message_.isEmpty())
     return message_;
 
   if(message_.isEmpty())
     return message_;
 
@@ -612,7 +641,10 @@ QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray
     return message_;
 
   QByteArray message = message_;
     return message_;
 
   QByteArray message = message_;
-  cipher->encrypt(message);
+  bool result = cipher->encrypt(message);
+  if(didEncrypt)
+    *didEncrypt = result;
+
   return message;
 }
 #endif
   return message;
 }
 #endif
index 801024d..9a31f69 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "corebasichandler.h"
 
 
 #include "corebasichandler.h"
 
+class Cipher;
 class Server;
 
 class CoreUserInputHandler : public CoreBasicHandler {
 class Server;
 
 class CoreUserInputHandler : public CoreBasicHandler {
@@ -77,11 +78,11 @@ protected:
 
 private:
   void banOrUnban(const BufferInfo &bufferInfo, const QString &text, bool ban);
 
 private:
   void banOrUnban(const BufferInfo &bufferInfo, const QString &text, bool ban);
-  void putPrivmsg(const QByteArray &target, const QByteArray &message, bool isEncrypted);
+  void putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher = 0);
   int lastParamOverrun(const QString &cmd, const QList<QByteArray> &params);
 
 #ifdef HAVE_QCA2
   int lastParamOverrun(const QString &cmd, const QList<QByteArray> &params);
 
 #ifdef HAVE_QCA2
-  QByteArray encrypt(const QString &target, const QByteArray &message) const;
+  QByteArray encrypt(const QString &target, const QByteArray &message, bool *didEncrypt = 0) const;
 #endif
 
   struct Command {
 #endif
 
   struct Command {