- implemented on request a chat monitor: a simple buffer which shows
[quassel.git] / src / core / ircserverhandler.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 #include "ircserverhandler.h"
21
22 #include "util.h"
23
24 #include "coresession.h"
25 #include "networkconnection.h"
26 #include "network.h"
27 #include "identity.h"
28 #include "ctcphandler.h"
29
30 #include "ircuser.h"
31 #include "ircchannel.h"
32
33 #include <QDebug>
34
35 IrcServerHandler::IrcServerHandler(NetworkConnection *parent)
36   : BasicHandler(parent),
37     networkConnection(parent) {
38 }
39
40 IrcServerHandler::~IrcServerHandler() {
41
42 }
43
44 QString IrcServerHandler::serverDecode(const QByteArray &string) {
45   return networkConnection->serverDecode(string);
46 }
47
48 QStringList IrcServerHandler::serverDecode(const QList<QByteArray> &stringlist) {
49   QStringList list;
50   foreach(QByteArray s, stringlist) list << networkConnection->serverDecode(s);
51   return list;
52 }
53
54 QString IrcServerHandler::bufferDecode(const QString &bufferName, const QByteArray &string) {
55   return networkConnection->bufferDecode(bufferName, string);
56 }
57
58 QStringList IrcServerHandler::bufferDecode(const QString &bufferName, const QList<QByteArray> &stringlist) {
59   QStringList list;
60   foreach(QByteArray s, stringlist) list << networkConnection->bufferDecode(bufferName, s);
61   return list;
62 }
63
64 QString IrcServerHandler::userDecode(const QString &userNick, const QByteArray &string) {
65   return networkConnection->userDecode(userNick, string);
66 }
67
68 QStringList IrcServerHandler::userDecode(const QString &userNick, const QList<QByteArray> &stringlist) {
69   QStringList list;
70   foreach(QByteArray s, stringlist) list << networkConnection->userDecode(userNick, s);
71   return list;
72 }
73
74 /*! Handle a raw message string sent by the server. We try to find a suitable handler, otherwise we call a default handler. */
75 void IrcServerHandler::handleServerMsg(QByteArray msg) {
76   try {
77     if(msg.isEmpty()) {
78       qWarning() << "Received empty string from server!";
79       return;
80     }
81
82     // Now we split the raw message into its various parts...
83     QString prefix = "";
84     QByteArray trailing;
85     QString cmd;
86
87     // First, check for a trailing parameter introduced by " :", since this might screw up splitting the msg
88     // NOTE: This assumes that this is true in raw encoding, but well, hopefully there are no servers running in japanese on protocol level...
89     int idx = msg.indexOf(" :");
90     if(idx >= 0) {
91       if(msg.length() > idx + 2) trailing = msg.mid(idx + 2);
92       msg = msg.left(idx);
93     }
94     // OK, now it is safe to split...
95     QList<QByteArray> params = msg.split(' ');
96     if(!trailing.isEmpty()) params << trailing;
97     if(params.count() < 1) {
98       qWarning() << "Received invalid string from server!";
99       return;
100     }
101
102     QString foo = serverDecode(params.takeFirst());
103
104     // a colon as the first chars indicates the existence of a prefix
105     if(foo[0] == ':') {
106       foo.remove(0, 1);
107       prefix = foo;
108       if(params.count() < 1) {
109         qWarning() << "Received invalid string from server!";
110         return;
111       }
112       foo = serverDecode(params.takeFirst());
113     }
114
115     // next string without a whitespace is the command
116     cmd = foo.trimmed().toUpper();
117
118     // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this!
119     uint num = cmd.toUInt();
120     if(num > 0) {
121       if(params.count() == 0) {
122         qWarning() << "Message received from server violates RFC and is ignored!";
123         return;
124       }
125       params.removeFirst();
126     }
127
128     // Now we try to find a handler for this message. BTW, I do love the Trolltech guys ;-)
129     handle(cmd, Q_ARG(QString, prefix), Q_ARG(QList<QByteArray>, params));
130     //handle(cmd, Q_ARG(QString, prefix));
131   } catch(Exception e) {
132     emit displayMsg(Message::Error, "", e.msg());
133   }
134 }
135
136
137 void IrcServerHandler::defaultHandler(QString cmd, QString prefix, QList<QByteArray> rawparams) {
138   // we assume that all this happens in server encoding
139   QStringList params;
140   foreach(QByteArray r, rawparams) params << serverDecode(r);
141   uint num = cmd.toUInt();
142   if(num) {
143     // A lot of server messages don't really need their own handler because they don't do much.
144     // Catch and handle these here.
145     switch(num) {
146       // Welcome, status, info messages. Just display these.
147       case 2: case 3: case 4: case 5: case 251: case 252: case 253: case 254: case 255: case 372: case 375:
148         emit displayMsg(Message::Server, "", params.join(" "), prefix);
149         break;
150       // Server error messages without param, just display them
151       case 409: case 411: case 412: case 422: case 424: case 445: case 446: case 451: case 462:
152       case 463: case 464: case 465: case 466: case 472: case 481: case 483: case 485: case 491: case 501: case 502:
153       case 431: // ERR_NONICKNAMEGIVEN 
154         emit displayMsg(Message::Error, "", params.join(" "), prefix);
155         break;
156       // Server error messages, display them in red. First param will be appended.
157       case 401: case 402: case 403: case 404: case 406: case 408: case 415: case 421: case 442:
158       { QString p = params.takeFirst();
159         emit displayMsg(Message::Error, "", params.join(" ") + " " + p, prefix);
160         break;
161       }
162       // Server error messages which will be displayed with a colon between the first param and the rest
163       case 413: case 414: case 423: case 441: case 444: case 461:
164       case 467: case 471: case 473: case 474: case 475: case 476: case 477: case 478: case 482:
165       case 436: // ERR_NICKCOLLISION
166       { QString p = params.takeFirst();
167         emit displayMsg(Message::Error, "", p + ": " + params.join(" "));
168         break;
169       }
170       // Ignore these commands.
171       case 366: case 376:
172         break;
173
174       // Everything else will be marked in red, so we can add them somewhere.
175       default:
176         emit displayMsg(Message::Error, "", cmd + " " + params.join(" "), prefix);
177     }
178     //qDebug() << prefix <<":"<<cmd<<params;
179   } else {
180     emit displayMsg(Message::Error, "", QString("Unknown: ") + cmd + " " + params.join(" "), prefix);
181     //qDebug() << prefix <<":"<<cmd<<params;
182   }
183 }
184
185 //******************************/
186 // IRC SERVER HANDLER
187 //******************************/
188 void IrcServerHandler::handleJoin(QString prefix, QList<QByteArray> params) {
189   Q_ASSERT(params.count() == 1);
190   QString channel = serverDecode(params[0]);
191   IrcUser *ircuser = network()->updateNickFromMask(prefix);
192   emit displayMsg(Message::Join, channel, channel, prefix);
193   //qDebug() << "IrcServerHandler::handleJoin()" << prefix << params;
194   ircuser->joinChannel(channel);
195 }
196
197 void IrcServerHandler::handleKick(QString prefix, QList<QByteArray> params) {
198   network()->updateNickFromMask(prefix);
199   IrcUser *victim = network()->ircUser(serverDecode(params[1]));
200   QString channel = serverDecode(params[0]);
201   Q_ASSERT(victim);
202
203   victim->partChannel(channel);
204
205   QString msg;
206   if(params.count() > 2) // someone got a reason!
207     msg = QString("%1 %2").arg(victim->nick()).arg(bufferDecode(channel, params[2]));
208   else
209     msg = victim->nick();
210
211   emit displayMsg(Message::Kick, channel, msg, prefix);
212 }
213
214 void IrcServerHandler::handleMode(QString prefix, QList<QByteArray> params) {
215   if(params.count() < 2) {
216     emit displayMsg(Message::Error, "", tr("Received invalid MODE from %s: %s").arg(prefix).arg(serverDecode(params).join(" ")));
217     return;
218   }
219
220   if(network()->isChannelName(params[0])) {
221     // Channel Modes
222     emit displayMsg(Message::Mode, serverDecode(params[0]), serverDecode(params).join(" "), prefix);
223
224     IrcChannel *channel = network()->ircChannel(params.takeFirst());
225     // FIXME: currently the IrcChannels only support PREFIX-Modes for users
226     // This cannot be fixed unless the SignalProxy() doesn't rely on methodIds anymore
227     QString modes = params.takeFirst();
228     bool add = true;
229     int modeIndex = 0;
230     for(int c = 0; c < modes.length(); c++) {
231       if(modes[c] == '+') {
232         add = true;
233         continue;
234       }
235       if(modes[c] == '-') {
236         add = false;
237         continue;
238       }
239
240       // this is the part where we restrict the mode changes to PREFIXES:
241       if(network()->prefixModes().contains(modes[c]) && modeIndex < params.count()) {
242         IrcUser *ircUser = network()->ircUser(params[modeIndex]);
243         if(add)
244           channel->addUserMode(ircUser, QString(modes[c]));
245         else
246           channel->removeUserMode(ircUser, QString(modes[c]));
247       }
248       modeIndex++;
249     }
250     
251   } else {
252     // pure User Modes
253     emit displayMsg(Message::Mode, "", serverDecode(params).join(" "), prefix);
254   }
255 }
256
257 void IrcServerHandler::handleNick(QString prefix, QList<QByteArray> params) {
258   IrcUser *ircuser = network()->updateNickFromMask(prefix);
259   Q_ASSERT(ircuser);
260   QString newnick = serverDecode(params[0]);
261   QString oldnick = ircuser->nick();
262
263   foreach(QString channel, ircuser->channels()) {
264     if(network()->isMyNick(oldnick)) {
265       emit displayMsg(Message::Nick, channel, newnick, newnick);
266     } else {
267       emit displayMsg(Message::Nick, channel, newnick, prefix);
268     }
269   }
270   ircuser->setNick(newnick);
271 }
272
273 void IrcServerHandler::handleNotice(QString prefix, QList<QByteArray> params) {
274   if(network()->currentServer().isEmpty() || network()->currentServer() == prefix)
275     emit displayMsg(Message::Server, "", serverDecode(params[1]), prefix);
276   else
277     emit displayMsg(Message::Notice, "", userDecode(prefix, params[1]), prefix);
278 }
279
280 void IrcServerHandler::handlePart(QString prefix, QList<QByteArray> params) {
281   IrcUser *ircuser = network()->updateNickFromMask(prefix);
282   QString channel = serverDecode(params[0]);
283   Q_ASSERT(ircuser);
284
285   ircuser->partChannel(channel);
286
287   QString msg;
288   if(params.count() > 1)
289     msg = userDecode(ircuser->nick(), params[1]);
290
291   emit displayMsg(Message::Part, channel, msg, prefix);
292 }
293
294 void IrcServerHandler::handlePing(QString prefix, QList<QByteArray> params) {
295   Q_UNUSED(prefix);
296   emit putCmd("PONG", serverDecode(params));
297 }
298
299 void IrcServerHandler::handlePrivmsg(QString prefix, QList<QByteArray> params) {
300   IrcUser *ircuser = network()->updateNickFromMask(prefix);
301   Q_ASSERT(ircuser);
302   if(params.count() < 2)
303     params << QByteArray("");
304
305   QString target = serverDecode(params[0]);
306
307   // are we the target or is it a channel?
308   if(network()->isMyNick(target)) {
309     // it's possible to pack multiple privmsgs into one param using ctcp
310     QStringList messages = networkConnection->ctcpHandler()->parse(CtcpHandler::CtcpQuery, prefix, target, userDecode(ircuser->nick(), params[1]));
311     quint8 flags;
312     foreach(QString message, messages) {
313       flags = Message::PrivMsg;
314       if(message.contains(network()->myNick()))
315         flags |= Message::Highlight;
316       emit displayMsg(Message::Plain, "", message, prefix, flags);
317     }
318   } else {
319     // so it's probably a channel..
320     if(!isChannelName(target)) {
321       qWarning() << "received PRIVMSG with target" << target << "which is neither us nor a channel!";
322       return;
323     }
324
325     QStringList messages = networkConnection->ctcpHandler()->parse(CtcpHandler::CtcpQuery, prefix, target, bufferDecode(target, params[1]));
326     quint8 flags;
327     foreach(QString message, messages) {
328       flags = Message::None;
329       if(message.contains(network()->myNick()))
330         flags |= Message::Highlight;
331       emit displayMsg(Message::Plain, target, message, prefix, flags);
332     }
333   }
334
335 }
336
337 void IrcServerHandler::handleQuit(QString prefix, QList<QByteArray> params) {
338   IrcUser *ircuser = network()->updateNickFromMask(prefix);
339   Q_ASSERT(ircuser);
340
341   QString msg;
342   if(params.count())
343     msg = userDecode(ircuser->nick(), params[0]);
344
345   foreach(QString channel, ircuser->channels())
346     emit displayMsg(Message::Quit, channel, msg, prefix);
347
348   network()->removeIrcUser(nickFromMask(prefix));
349 }
350
351 void IrcServerHandler::handleTopic(QString prefix, QList<QByteArray> params) {
352   IrcUser *ircuser = network()->updateNickFromMask(prefix);
353   QString channel = serverDecode(params[0]);
354   QString topic = bufferDecode(channel, params[1]);
355   Q_ASSERT(ircuser);
356
357   network()->ircChannel(channel)->setTopic(topic);
358
359   emit displayMsg(Message::Server, channel, tr("%1 has changed topic for %2 to: \"%3\"").arg(ircuser->nick()).arg(channel).arg(topic));
360 }
361
362 /* RPL_WELCOME */
363 void IrcServerHandler::handle001(QString prefix, QList<QByteArray> params) {
364   // there should be only one param: "Welcome to the Internet Relay Network <nick>!<user>@<host>"
365   QString param = serverDecode(params[0]);
366   QString myhostmask = param.section(' ', -1, -1);
367   network()->setCurrentServer(prefix);
368   network()->setMyNick(nickFromMask(myhostmask));
369
370   emit displayMsg(Message::Server, "", param, prefix);
371 }
372
373 /* RPL_ISUPPORT */
374 // TODO Complete 005 handling, also use sensible defaults for non-sent stuff
375 void IrcServerHandler::handle005(QString prefix, QList<QByteArray> params) {
376   Q_UNUSED(prefix)
377   QString rpl_isupport_suffix = serverDecode(params.takeLast());
378   if(rpl_isupport_suffix.toLower() != QString("are supported by this server")) {
379     qWarning() << "Received invalid RPL_ISUPPORT! Suffix is:" << rpl_isupport_suffix << "Excpected: are supported by this server";
380     return;
381   }
382   
383   foreach(QString param, serverDecode(params)) {
384     QString key = param.section("=", 0, 0);
385     QString value = param.section("=", 1);
386     network()->addSupport(key, value);
387   }
388 }
389
390
391 /* RPL_NOTOPIC */
392 void IrcServerHandler::handle331(QString prefix, QList<QByteArray> params) {
393   Q_UNUSED(prefix);
394   QString channel = serverDecode(params[0]);
395   network()->ircChannel(channel)->setTopic(QString());
396   emit displayMsg(Message::Server, channel, tr("No topic is set for %1.").arg(channel));
397 }
398
399 /* RPL_TOPIC */
400 void IrcServerHandler::handle332(QString prefix, QList<QByteArray> params) {
401   Q_UNUSED(prefix);
402   QString channel = serverDecode(params[0]);
403   QString topic = bufferDecode(channel, params[1]);
404   network()->ircChannel(channel)->setTopic(topic);
405   emit displayMsg(Message::Server, channel, tr("Topic for %1 is \"%2\"").arg(channel, topic));
406 }
407
408 /* Topic set by... */
409 void IrcServerHandler::handle333(QString prefix, QList<QByteArray> params) {
410   Q_UNUSED(prefix);
411   QString channel = serverDecode(params[0]);
412   emit displayMsg(Message::Server, channel, tr("Topic set by %1 on %2")
413       .arg(bufferDecode(channel, params[1]), QDateTime::fromTime_t(bufferDecode(channel, params[2]).toUInt()).toString()));
414 }
415
416 /* RPL_NAMREPLY */
417 void IrcServerHandler::handle353(QString prefix, QList<QByteArray> params) {
418   Q_UNUSED(prefix)
419   params.removeFirst(); // either "=", "*" or "@" indicating a public, private or secret channel
420   QString channelname = serverDecode(params.takeFirst());
421
422   foreach(QString nick, serverDecode(params.takeFirst()).split(' ')) {
423     QString mode = QString();
424
425     if(network()->prefixes().contains(nick[0])) {
426       mode = network()->prefixToMode(nick[0]);
427       nick = nick.mid(1);
428     }
429
430     IrcUser *ircuser = network()->newIrcUser(nick);
431     ircuser->joinChannel(channelname);
432
433     if(!mode.isNull())
434       network()->ircChannel(channelname)->addUserMode(ircuser, mode);
435   }
436 }
437
438 /* ERR_ERRONEUSNICKNAME */
439 void IrcServerHandler::handle432(QString prefix, QList<QByteArray> params) {
440   Q_UNUSED(prefix);
441
442   if(params.size() < 2) {
443     // handle unreal-ircd bug, where unreal ircd doesnt supply a TARGET in ERR_ERRONEUSNICKNAME during registration phase:
444     // nick @@@
445     // :irc.scortum.moep.net 432  @@@ :Erroneous Nickname: Illegal characters
446     // correct server reply:
447     // :irc.scortum.moep.net 432 * @@@ :Erroneous Nickname: Illegal characters
448     emit displayMsg(Message::Error, "", tr("There is a nickname in your identity's nicklist which contains illegal characters"));
449     emit displayMsg(Message::Error, "", tr("Due to a bug in Unreal IRCd (and maybe other irc-servers too) we're unable to determine the erroneous nick"));
450     emit displayMsg(Message::Error, "", tr("Please use: /nick <othernick> to continue or clean up your nicklist"));
451   } else {
452     QString errnick = params[0];
453     emit displayMsg(Message::Error, "", tr("Nick %1 contains illegal characters").arg(errnick));
454     tryNextNick(errnick);
455   }
456 }
457
458 /* ERR_NICKNAMEINUSE */
459 void IrcServerHandler::handle433(QString prefix, QList<QByteArray> params) {
460   Q_UNUSED(prefix);
461
462   QString errnick = serverDecode(params[0]);
463   emit displayMsg(Message::Error, "", tr("Nick already in use: %1").arg(errnick));
464
465   // if there is a problem while connecting to the server -> we handle it
466   // but only if our connection has not been finished yet...
467   if(!networkConnection->network()->currentServer().isEmpty())
468     return;
469
470   tryNextNick(errnick);
471 }
472
473 void IrcServerHandler::tryNextNick(const QString &errnick) {
474   QStringList desiredNicks = networkConnection->coreSession()->identity(networkConnection->network()->identity())->nicks();
475   int nextNick = desiredNicks.indexOf(errnick) + 1;
476   if(desiredNicks.size() > nextNick) {
477     putCmd("NICK", QStringList(desiredNicks[nextNick]));
478   } else {
479     emit displayMsg(Message::Error, "", tr("No free and valid nicks in nicklist found. use: /nick <othernick> to continue"));
480   }
481 }
482
483
484 /***********************************************************************************/
485
486