Fixing issues with multiple CTCP requests in one message
[quassel.git] / src / core / ctcphandler.cpp
index 87ba8c9..758d2ef 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-08 by the Quassel Project                          *
+ *   Copyright (C) 2005-10 by the Quassel Project                          *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  ***************************************************************************/
 #include "ctcphandler.h"
 
-#include "util.h"
 #include "message.h"
+#include "network.h"
+#include "quassel.h"
+#include "util.h"
+#include "coreignorelistmanager.h"
 
-CtcpHandler::CtcpHandler(Server *parent)
-  : BasicHandler(parent) {
+CtcpHandler::CtcpHandler(CoreNetwork *parent)
+  : CoreBasicHandler(parent),
+    XDELIM("\001"),
+    _ignoreListManager(parent->ignoreListManager())
+{
 
-  QString MQUOTE = QString('\020');
-  ctcpMDequoteHash[MQUOTE + '0'] = QString('\000');
-  ctcpMDequoteHash[MQUOTE + 'n'] = QString('\n');
-  ctcpMDequoteHash[MQUOTE + 'r'] = QString('\r');
+  QByteArray MQUOTE = QByteArray("\020");
+  ctcpMDequoteHash[MQUOTE + '0'] = QByteArray(1, '\000');
+  ctcpMDequoteHash[MQUOTE + 'n'] = QByteArray(1, '\n');
+  ctcpMDequoteHash[MQUOTE + 'r'] = QByteArray(1, '\r');
   ctcpMDequoteHash[MQUOTE + MQUOTE] = MQUOTE;
 
-  XDELIM = QString('\001');
-  QString XQUOTE = QString('\134');
+  QByteArray XQUOTE = QByteArray("\134");
   ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE;
-  ctcpXDelimDequoteHash[XQUOTE + QString('a')] = XDELIM;
+  ctcpXDelimDequoteHash[XQUOTE + QByteArray("a")] = XDELIM;
 }
 
-QString CtcpHandler::dequote(QString message) {
-  QString dequotedMessage;
-  QString messagepart;
-  QHash<QString, QString>::iterator ctcpquote;
-  
+QByteArray CtcpHandler::lowLevelQuote(const QByteArray &message) {
+  QByteArray quotedMessage = message;
+
+  QHash<QByteArray, QByteArray> quoteHash = ctcpMDequoteHash;
+  QByteArray MQUOTE = QByteArray("\020");
+  quoteHash.remove(MQUOTE + MQUOTE);
+  quotedMessage.replace(MQUOTE, MQUOTE + MQUOTE);
+
+  QHash<QByteArray, QByteArray>::const_iterator quoteIter = quoteHash.constBegin();
+  while(quoteIter != quoteHash.constEnd()) {
+    quotedMessage.replace(quoteIter.value(), quoteIter.key());
+    quoteIter++;
+  }
+  return quotedMessage;
+}
+
+QByteArray CtcpHandler::lowLevelDequote(const QByteArray &message) {
+  QByteArray dequotedMessage;
+  QByteArray messagepart;
+  QHash<QByteArray, QByteArray>::iterator ctcpquote;
+
   // copy dequote Message
   for(int i = 0; i < message.size(); i++) {
-    messagepart = message[i];
+    messagepart = message.mid(i,1);
     if(i+1 < message.size()) {
       for(ctcpquote = ctcpMDequoteHash.begin(); ctcpquote != ctcpMDequoteHash.end(); ++ctcpquote) {
         if(message.mid(i,2) == ctcpquote.key()) {
-          dequotedMessage += ctcpquote.value();
+          messagepart = ctcpquote.value();
           i++;
           break;
         }
@@ -59,14 +80,23 @@ QString CtcpHandler::dequote(QString message) {
   return dequotedMessage;
 }
 
+QByteArray CtcpHandler::xdelimQuote(const QByteArray &message) {
+  QByteArray quotedMessage = message;
+  QHash<QByteArray, QByteArray>::const_iterator quoteIter = ctcpXDelimDequoteHash.constBegin();
+  while(quoteIter != ctcpXDelimDequoteHash.constEnd()) {
+    quotedMessage.replace(quoteIter.value(), quoteIter.key());
+    quoteIter++;
+  }
+  return quotedMessage;
+}
 
-QString CtcpHandler::XdelimDequote(QString message) {
-  QString dequotedMessage;
-  QString messagepart;
-  QHash<QString, QString>::iterator xdelimquote;
+QByteArray CtcpHandler::xdelimDequote(const QByteArray &message) {
+  QByteArray dequotedMessage;
+  QByteArray messagepart;
+  QHash<QByteArray, QByteArray>::iterator xdelimquote;
 
   for(int i = 0; i < message.size(); i++) {
-    messagepart = message[i];
+    messagepart = message.mid(i,1);
     if(i+1 < message.size()) {
       for(xdelimquote = ctcpXDelimDequoteHash.begin(); xdelimquote != ctcpXDelimDequoteHash.end(); ++xdelimquote) {
         if(message.mid(i,2) == xdelimquote.key()) {
@@ -81,75 +111,158 @@ QString CtcpHandler::XdelimDequote(QString message) {
   return dequotedMessage;
 }
 
-QStringList CtcpHandler::parse(CtcpType ctcptype, QString prefix, QString target, QString message) {
-  QStringList messages;
-  QString ctcp;
-  
+void CtcpHandler::parse(Message::Type messageType, const QString &prefix, const QString &target, const QByteArray &message) {
+  QByteArray ctcp;
+
   //lowlevel message dequote
-  QString dequotedMessage = dequote(message);
-  
+  QByteArray dequotedMessage = lowLevelDequote(message);
+
+  CtcpType ctcptype = messageType == Message::Notice
+    ? CtcpReply
+    : CtcpQuery;
+
+  Message::Flags flags = (messageType == Message::Notice && !network()->isChannelName(target))
+    ? Message::Redirected
+    : Message::None;
+
   // extract tagged / extended data
-  while(dequotedMessage.contains(XDELIM)) {
-    messages << dequotedMessage.section(XDELIM,0,0);
-    ctcp = XdelimDequote(dequotedMessage.section(XDELIM,1,1));
-    dequotedMessage = dequotedMessage.section(XDELIM,2,2);
-    
+  int xdelimPos = -1;
+  int xdelimEndPos = -1;
+  int spacePos = -1;
+  QList<QByteArray> replies;
+  while((xdelimPos = dequotedMessage.indexOf(XDELIM)) != -1) {
+    if(xdelimPos > 0)
+      displayMsg(messageType, target, userDecode(target, dequotedMessage.left(xdelimPos)), prefix, flags);
+
+    xdelimEndPos = dequotedMessage.indexOf(XDELIM, xdelimPos + 1);
+    if(xdelimEndPos == -1) {
+      // no matching end delimiter found... treat rest of the message as ctcp
+      xdelimEndPos = dequotedMessage.count();
+    }
+    ctcp = xdelimDequote(dequotedMessage.mid(xdelimPos + 1, xdelimEndPos - xdelimPos - 1));
+    dequotedMessage = dequotedMessage.mid(xdelimEndPos + 1);
+
     //dispatch the ctcp command
-    QString ctcpcmd = ctcp.section(' ', 0, 0);
-    QString ctcpparam = ctcp.section(' ', 1);
+    QString ctcpcmd = userDecode(target, ctcp.left(spacePos));
+    QString ctcpparam = userDecode(target, ctcp.mid(spacePos + 1));
 
-    handle(ctcpcmd, Q_ARG(CtcpType, ctcptype), Q_ARG(QString, prefix), Q_ARG(QString, target), Q_ARG(QString, ctcpparam));
+    spacePos = ctcp.indexOf(' ');
+    if(spacePos != -1) {
+      ctcpcmd = userDecode(target, ctcp.left(spacePos));
+      ctcpparam = userDecode(target, ctcp.mid(spacePos + 1));
+    } else {
+      ctcpcmd = userDecode(target, ctcp);
+      ctcpparam = QString();
+    }
+
+    if(!_ignoreListManager->ctcpMatch(prefix, network()->networkName(), ctcpcmd.toUpper())) {
+      QString reply_;
+      handle(ctcpcmd, Q_ARG(CtcpType, ctcptype), Q_ARG(QString, prefix), Q_ARG(QString, target), Q_ARG(QString, ctcpparam), Q_ARG(QString, reply_));
+      if(ctcptype == CtcpQuery && !reply_.isNull()) {
+        replies << lowLevelQuote(pack(serverEncode(ctcpcmd), userEncode(nickFromMask(prefix), reply_)));
+      }
+    }
   }
-  if(!dequotedMessage.isEmpty()) {
-    messages << dequotedMessage;
+  if(ctcptype == CtcpQuery && !replies.isEmpty()) {
+    packedReply(nickFromMask(prefix), replies);
   }
-  return messages;
+
+  if(!dequotedMessage.isEmpty())
+    displayMsg(messageType, target, userDecode(target, dequotedMessage), prefix, flags);
 }
 
-QString CtcpHandler::pack(QString ctcpTag, QString message) {
-  return XDELIM + ctcpTag + ' ' + message + XDELIM;
+QByteArray CtcpHandler::pack(const QByteArray &ctcpTag, const QByteArray &message) {
+  if(message.isEmpty())
+    return XDELIM + ctcpTag + XDELIM;
+
+  return XDELIM + ctcpTag + ' ' + xdelimQuote(message) + XDELIM;
 }
 
-void CtcpHandler::query(QString bufname, QString ctcpTag, QString message) {
-  QStringList params;
-  params << bufname << pack(ctcpTag, message);
-  emit putCmd("PRIVMSG", params); 
+void CtcpHandler::query(const QString &bufname, const QString &ctcpTag, const QString &message) {
+  QList<QByteArray> params;
+  params << serverEncode(bufname) << lowLevelQuote(pack(serverEncode(ctcpTag), userEncode(bufname, message)));
+  emit putCmd("PRIVMSG", params);
 }
 
-void CtcpHandler::reply(QString bufname, QString ctcpTag, QString message) {
-  QStringList params;
-  params << bufname << pack(ctcpTag, message);
+void CtcpHandler::reply(const QString &bufname, const QString &ctcpTag, const QString &message) {
+  QList<QByteArray> params;
+  params << serverEncode(bufname) << lowLevelQuote(pack(serverEncode(ctcpTag), userEncode(bufname, message)));
+  emit putCmd("NOTICE", params);
+}
+
+void CtcpHandler::packedReply(const QString &bufname, const QList<QByteArray> &replies) {
+  QList<QByteArray> params;
+
+  int answerSize = 0;
+  for(int i = 0; i < replies.count(); i++) {
+    answerSize += replies.at(i).size();
+  }
+
+  QByteArray quotedReply(answerSize, 0);
+  int nextPos = 0;
+  QByteArray &reply = quotedReply;
+  for(int i = 0; i < replies.count(); i++) {
+    reply = replies.at(i);
+    quotedReply.replace(nextPos, reply.size(), reply);
+    nextPos += reply.size();
+  }
+
+  params << serverEncode(bufname) << quotedReply;
   emit putCmd("NOTICE", params);
 }
 
 //******************************/
 // CTCP HANDLER
 //******************************/
-void CtcpHandler::handleAction(CtcpType ctcptype, QString prefix, QString target, QString param) {
-  emit displayMsg(Message::Action, target, param, prefix);
+void CtcpHandler::handleAction(CtcpType ctcptype, const QString &prefix, const QString &target, const QString &param, QString &/*reply*/) {
+  Q_UNUSED(ctcptype)
+  emit displayMsg(Message::Action, typeByTarget(target), target, param, prefix);
 }
 
-void CtcpHandler::handlePing(CtcpType ctcptype, QString prefix, QString target, QString param) {
+void CtcpHandler::handlePing(CtcpType ctcptype, const QString &prefix, const QString &target, const QString &param, QString &reply) {
+  Q_UNUSED(target)
   if(ctcptype == CtcpQuery) {
-    reply(nickFromMask(prefix), "PING", param);
-    emit displayMsg(Message::Server, "", tr("Received CTCP PING request from %1").arg(prefix));
+    reply = param;
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP PING request from %1").arg(prefix));
   } else {
     // display ping answer
+    uint now = QDateTime::currentDateTime().toTime_t();
+    uint then = QDateTime().fromTime_t(param.toInt()).toTime_t();
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP PING answer from %1 with %2 seconds round trip time")
+                    .arg(nickFromMask(prefix)).arg(now-then));
   }
 }
 
-void CtcpHandler::handleVersion(CtcpType ctcptype, QString prefix, QString target, QString param) {
+void CtcpHandler::handleVersion(CtcpType ctcptype, const QString &prefix, const QString &target, const QString &param, QString &reply) {
+  Q_UNUSED(target)
   if(ctcptype == CtcpQuery) {
-    // FIXME use real Info about quassel :)
-    reply(nickFromMask(prefix), "VERSION", QString("Quassel IRC (Pre-Release) - http://www.quassel-irc.org"));
-    emit displayMsg(Message::Server, "", tr("Received CTCP VERSION request by %1").arg(prefix));
+    reply = QString("Quassel IRC %1 (built on %2) -- http://www.quassel-irc.org").arg(Quassel::buildInfo().plainVersionString).arg(Quassel::buildInfo().buildDate);
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP VERSION request by %1").arg(prefix));
   } else {
-    // TODO display Version answer
+    // display Version answer
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP VERSION answer from %1: %2")
+                    .arg(nickFromMask(prefix)).arg(param));
   }
 }
 
-void CtcpHandler::defaultHandler(QString cmd, CtcpType ctcptype, QString prefix, QString target, QString param) {
-  emit displayMsg(Message::Error, "", tr("Received unknown CTCP %1 by %2").arg(cmd).arg(prefix));
+void CtcpHandler::handleTime(CtcpType ctcptype, const QString &prefix, const QString &target, const QString &param, QString &reply) {
+  Q_UNUSED(target)
+  if(ctcptype == CtcpQuery) {
+    reply = QDateTime::currentDateTime().toString();
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP TIME request by %1").arg(prefix));
+  } else {
+    emit displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Received CTCP TIME answer from %1: %2")
+                    .arg(nickFromMask(prefix)).arg(param));
+  }
 }
 
+void CtcpHandler::defaultHandler(const QString &cmd, CtcpType ctcptype, const QString &prefix, const QString &target, const QString &param, QString &reply) {
+  Q_UNUSED(ctcptype);
+  Q_UNUSED(target);
+  Q_UNUSED(reply);
+  QString str = tr("Received unknown CTCP %1 by %2").arg(cmd).arg(prefix);
+  if(!param.isEmpty())
+    str.append(tr(" with arguments: %1").arg(param));
+  emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", str);
+}