e4b094d22f1ef2d0a7c4c7ee87fcd838e3406dd6
[quassel.git] / src / core / coreuserinputhandler.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2012 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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "coreuserinputhandler.h"
22
23 #include "util.h"
24
25 #include "ctcpparser.h"
26
27 #include <QRegExp>
28
29 #ifdef HAVE_QCA2
30 #  include "cipher.h"
31 #endif
32
33 CoreUserInputHandler::CoreUserInputHandler(CoreNetwork *parent)
34     : CoreBasicHandler(parent)
35 {
36 }
37
38
39 void CoreUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg)
40 {
41     if (msg.isEmpty())
42         return;
43
44     AliasManager::CommandList list = coreSession()->aliasManager().processInput(bufferInfo, msg);
45
46     for (int i = 0; i < list.count(); i++) {
47         QString cmd = list.at(i).second.section(' ', 0, 0).remove(0, 1).toUpper();
48         QString payload = list.at(i).second.section(' ', 1);
49         handle(cmd, Q_ARG(BufferInfo, list.at(i).first), Q_ARG(QString, payload));
50     }
51 }
52
53
54 // ====================
55 //  Public Slots
56 // ====================
57 void CoreUserInputHandler::handleAway(const BufferInfo &bufferInfo, const QString &msg)
58 {
59     Q_UNUSED(bufferInfo)
60     if (msg.startsWith("-all")) {
61         if (msg.length() == 4) {
62             coreSession()->globalAway();
63             return;
64         }
65         Q_ASSERT(msg.length() > 4);
66         if (msg[4] == ' ') {
67             coreSession()->globalAway(msg.mid(5));
68             return;
69         }
70     }
71     issueAway(msg);
72 }
73
74
75 void CoreUserInputHandler::issueAway(const QString &msg, bool autoCheck)
76 {
77     QString awayMsg = msg;
78     IrcUser *me = network()->me();
79
80     // if there is no message supplied we have to check if we are already away or not
81     if (autoCheck && msg.isEmpty()) {
82         if (me && !me->isAway()) {
83             Identity *identity = network()->identityPtr();
84             if (identity) {
85                 awayMsg = identity->awayReason();
86             }
87             if (awayMsg.isEmpty()) {
88                 awayMsg = tr("away");
89             }
90         }
91     }
92     if (me)
93         me->setAwayMessage(awayMsg);
94
95     putCmd("AWAY", serverEncode(awayMsg));
96 }
97
98
99 void CoreUserInputHandler::handleBan(const BufferInfo &bufferInfo, const QString &msg)
100 {
101     banOrUnban(bufferInfo, msg, true);
102 }
103
104
105 void CoreUserInputHandler::handleUnban(const BufferInfo &bufferInfo, const QString &msg)
106 {
107     banOrUnban(bufferInfo, msg, false);
108 }
109
110
111 void CoreUserInputHandler::banOrUnban(const BufferInfo &bufferInfo, const QString &msg, bool ban)
112 {
113     QString banChannel;
114     QString banUser;
115
116     QStringList params = msg.split(" ");
117
118     if (!params.isEmpty() && isChannelName(params[0])) {
119         banChannel = params.takeFirst();
120     }
121     else if (bufferInfo.type() == BufferInfo::ChannelBuffer) {
122         banChannel = bufferInfo.bufferName();
123     }
124     else {
125         emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", QString("Error: channel unknown in command: /BAN %1").arg(msg));
126         return;
127     }
128
129     if (!params.isEmpty() && !params.contains("!") && network()->ircUser(params[0])) {
130         IrcUser *ircuser = network()->ircUser(params[0]);
131         // generalizedHost changes <nick> to  *!ident@*.sld.tld.
132         QString generalizedHost = ircuser->host();
133         if (generalizedHost.isEmpty()) {
134             emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", QString("Error: host unknown in command: /BAN %1").arg(msg));
135             return;
136         }
137
138         static QRegExp ipAddress("\\d+\\.\\d+\\.\\d+\\.\\d+");
139         if (ipAddress.exactMatch(generalizedHost))    {
140             int lastDotPos = generalizedHost.lastIndexOf('.') + 1;
141             generalizedHost.replace(lastDotPos, generalizedHost.length() - lastDotPos, '*');
142         }
143         else if (generalizedHost.lastIndexOf(".") != -1 && generalizedHost.lastIndexOf(".", generalizedHost.lastIndexOf(".")-1) != -1) {
144             int secondLastPeriodPosition = generalizedHost.lastIndexOf(".", generalizedHost.lastIndexOf(".")-1);
145             generalizedHost.replace(0, secondLastPeriodPosition, "*");
146         }
147         banUser = QString("*!%1@%2").arg(ircuser->user(), generalizedHost);
148     }
149     else {
150         banUser = params.join(" ");
151     }
152
153     QString banMode = ban ? "+b" : "-b";
154     QString banMsg = QString("MODE %1 %2 %3").arg(banChannel, banMode, banUser);
155     emit putRawLine(serverEncode(banMsg));
156 }
157
158
159 void CoreUserInputHandler::handleCtcp(const BufferInfo &bufferInfo, const QString &msg)
160 {
161     Q_UNUSED(bufferInfo)
162
163     QString nick = msg.section(' ', 0, 0);
164     QString ctcpTag = msg.section(' ', 1, 1).toUpper();
165     if (ctcpTag.isEmpty())
166         return;
167
168     QString message = msg.section(' ', 2);
169     QString verboseMessage = tr("sending CTCP-%1 request to %2").arg(ctcpTag).arg(nick);
170
171     if (ctcpTag == "PING") {
172         uint now = QDateTime::currentDateTime().toTime_t();
173         message = QString::number(now);
174     }
175
176     // FIXME make this a proper event
177     coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), nick, ctcpTag, message);
178     emit displayMsg(Message::Action, BufferInfo::StatusBuffer, "", verboseMessage, network()->myNick());
179 }
180
181
182 void CoreUserInputHandler::handleDelkey(const BufferInfo &bufferInfo, const QString &msg)
183 {
184 #ifdef HAVE_QCA2
185     if (!bufferInfo.isValid())
186         return;
187
188     if (!Cipher::neededFeaturesAvailable())
189         return;
190
191     QStringList parms = msg.split(' ', QString::SkipEmptyParts);
192
193     if (parms.isEmpty() && !bufferInfo.bufferName().isEmpty())
194         parms.prepend(bufferInfo.bufferName());
195
196     if (parms.isEmpty()) {
197         emit displayMsg(Message::Info, bufferInfo.bufferName(), "",
198             tr("[usage] /delkey <nick|channel> deletes the encryption key for nick or channel or just /delkey when in a channel or query."));
199         return;
200     }
201
202     QString target = parms.at(0);
203
204     if (network()->cipherKey(target).isEmpty()) {
205         emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("No key has been set for %1.").arg(target));
206         return;
207     }
208
209     network()->setCipherKey(target, QByteArray());
210
211     if (network()->isChannelName(target) && network()->channels().contains(target)) {
212         qobject_cast<CoreIrcChannel *>(network()->ircChannel(target))->setEncrypted(false);
213     }
214     else if (network()->nicks().contains(target)) {
215         qobject_cast<CoreIrcUser *>(network()->ircUser(target))->setEncrypted(false);
216     }
217
218     emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("The key for %1 has been deleted.").arg(target));
219
220 #else
221     Q_UNUSED(msg)
222     emit displayMsg(Message::Error, bufferInfo.bufferName(), "", tr("Error: Setting an encryption key requires Quassel to have been built "
223                                                                     "with support for the Qt Cryptographic Architecture (QCA2) library. "
224                                                                     "Contact your distributor about a Quassel package with QCA2 "
225                                                                     "support, or rebuild Quassel with QCA2 present."));
226 #endif
227 }
228
229 void CoreUserInputHandler::doMode(const BufferInfo &bufferInfo, const QChar& addOrRemove, const QChar& mode, QString nicks)
230 {
231     QString m;
232     bool isNumber;
233     int maxModes = network()->support("MODES").toInt(&isNumber);
234     if (!isNumber || maxModes == 0) maxModes = 1;
235     
236     QStringList nickList;
237     if (nicks == "*") { // All users in channel
238         const QList<IrcUser*> users = network()->ircChannel(bufferInfo.bufferName())->ircUsers();
239         foreach(IrcUser *user, users) {
240             if ((addOrRemove == '+' && !network()->ircChannel(bufferInfo.bufferName())->userModes(user).contains(mode))
241                 || (addOrRemove == '-' && network()->ircChannel(bufferInfo.bufferName())->userModes(user).contains(mode)))
242                 nickList.append(user->nick());
243         }
244     } else { 
245         nickList = nicks.split(' ', QString::SkipEmptyParts);
246     }
247     
248     if (nickList.count() == 0) return;
249     
250     while (!nickList.isEmpty()) {
251         int amount = qMin(nickList.count(), maxModes);
252         QString m = addOrRemove; for(int i = 0; i < amount; i++) m += mode;
253         QStringList params;
254         params << bufferInfo.bufferName() << m;
255         for(int i = 0; i < amount; i++) params << nickList.takeFirst();
256         emit putCmd("MODE", serverEncode(params));
257     }
258 }
259
260
261 void CoreUserInputHandler::handleDeop(const BufferInfo &bufferInfo, const QString &nicks)
262 {
263     doMode(bufferInfo, '-', 'o', nicks);
264 }
265
266
267 void CoreUserInputHandler::handleDehalfop(const BufferInfo &bufferInfo, const QString &nicks)
268 {
269     doMode(bufferInfo, '-', 'h', nicks);
270 }
271
272
273 void CoreUserInputHandler::handleDevoice(const BufferInfo &bufferInfo, const QString &nicks)
274 {
275     doMode(bufferInfo, '-', 'v', nicks);
276 }
277
278 void CoreUserInputHandler::handleHalfop(const BufferInfo &bufferInfo, const QString &nicks)
279 {
280     doMode(bufferInfo, '+', 'h', nicks);
281 }
282
283 void CoreUserInputHandler::handleOp(const BufferInfo &bufferInfo, const QString &nicks) {
284   doMode(bufferInfo, '+', 'o', nicks);
285 }
286
287
288 void CoreUserInputHandler::handleInvite(const BufferInfo &bufferInfo, const QString &msg)
289 {
290     QStringList params;
291     params << msg << bufferInfo.bufferName();
292     emit putCmd("INVITE", serverEncode(params));
293 }
294
295
296 void CoreUserInputHandler::handleJoin(const BufferInfo &bufferInfo, const QString &msg)
297 {
298     Q_UNUSED(bufferInfo);
299
300     // trim spaces before chans or keys
301     QString sane_msg = msg;
302     sane_msg.replace(QRegExp(", +"), ",");
303     QStringList params = sane_msg.trimmed().split(" ");
304
305     QStringList chans = params[0].split(",", QString::SkipEmptyParts);
306     QStringList keys;
307     if (params.count() > 1)
308         keys = params[1].split(",");
309
310     int i;
311     for (i = 0; i < chans.count(); i++) {
312         if (!network()->isChannelName(chans[i]))
313             chans[i].prepend('#');
314
315         if (i < keys.count()) {
316             network()->addChannelKey(chans[i], keys[i]);
317         }
318         else {
319             network()->removeChannelKey(chans[i]);
320         }
321     }
322
323     static const char *cmd = "JOIN";
324     i = 0;
325     QStringList joinChans, joinKeys;
326     int slicesize = chans.count();
327     QList<QByteArray> encodedParams;
328
329     // go through all to-be-joined channels and (re)build the join list
330     while (i < chans.count()) {
331         joinChans.append(chans.at(i));
332         if (i < keys.count())
333             joinKeys.append(keys.at(i));
334
335         // if the channel list we built so far either contains all requested channels or exceeds
336         // the desired amount of channels in this slice, try to send what we have so far
337         if (++i == chans.count() || joinChans.count() >= slicesize) {
338             params.clear();
339             params.append(joinChans.join(","));
340             params.append(joinKeys.join(","));
341             encodedParams = serverEncode(params);
342             // check if it fits in one command
343             if (lastParamOverrun(cmd, encodedParams) == 0) {
344                 emit putCmd(cmd, encodedParams);
345             }
346             else if (slicesize > 1) {
347                 // back to start of slice, try again with half the amount of channels
348                 i -= slicesize;
349                 slicesize /= 2;
350             }
351             joinChans.clear();
352             joinKeys.clear();
353         }
354     }
355 }
356
357
358 void CoreUserInputHandler::handleKick(const BufferInfo &bufferInfo, const QString &msg)
359 {
360     QString nick = msg.section(' ', 0, 0, QString::SectionSkipEmpty);
361     QString reason = msg.section(' ', 1, -1, QString::SectionSkipEmpty).trimmed();
362     if (reason.isEmpty())
363         reason = network()->identityPtr()->kickReason();
364
365     QList<QByteArray> params;
366     params << serverEncode(bufferInfo.bufferName()) << serverEncode(nick) << channelEncode(bufferInfo.bufferName(), reason);
367     emit putCmd("KICK", params);
368 }
369
370
371 void CoreUserInputHandler::handleKill(const BufferInfo &bufferInfo, const QString &msg)
372 {
373     Q_UNUSED(bufferInfo)
374     QString nick = msg.section(' ', 0, 0, QString::SectionSkipEmpty);
375     QString pass = msg.section(' ', 1, -1, QString::SectionSkipEmpty);
376     QList<QByteArray> params;
377     params << serverEncode(nick) << serverEncode(pass);
378     emit putCmd("KILL", params);
379 }
380
381
382 void CoreUserInputHandler::handleList(const BufferInfo &bufferInfo, const QString &msg)
383 {
384     Q_UNUSED(bufferInfo)
385     emit putCmd("LIST", serverEncode(msg.split(' ', QString::SkipEmptyParts)));
386 }
387
388
389 void CoreUserInputHandler::handleMe(const BufferInfo &bufferInfo, const QString &msg)
390 {
391     if (bufferInfo.bufferName().isEmpty()) return;  // server buffer
392     // FIXME make this a proper event
393     coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(), "ACTION", msg);
394     emit displayMsg(Message::Action, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
395 }
396
397
398 void CoreUserInputHandler::handleMode(const BufferInfo &bufferInfo, const QString &msg)
399 {
400     Q_UNUSED(bufferInfo)
401
402     QStringList params = msg.split(' ', QString::SkipEmptyParts);
403     // if the first argument is neither a channel nor us (user modes are only to oneself) the current buffer is assumed to be the target
404     if (!params.isEmpty()) {
405         if (!network()->isChannelName(params[0]) && !network()->isMyNick(params[0]))
406             params.prepend(bufferInfo.bufferName());
407         if (network()->isMyNick(params[0]) && params.count() == 2)
408             network()->updateIssuedModes(params[1]);
409         if (params[0] == "-reset" && params.count() == 1) {
410             // FIXME: give feedback to the user (I don't want to add new strings right now)
411             network()->resetPersistentModes();
412             return;
413         }
414     }
415
416     // TODO handle correct encoding for buffer modes (channelEncode())
417     emit putCmd("MODE", serverEncode(params));
418 }
419
420
421 // TODO: show privmsgs
422 void CoreUserInputHandler::handleMsg(const BufferInfo &bufferInfo, const QString &msg)
423 {
424     Q_UNUSED(bufferInfo);
425     if (!msg.contains(' '))
426         return;
427
428     QString target = msg.section(' ', 0, 0);
429     QByteArray encMsg = userEncode(target, msg.section(' ', 1));
430
431 #ifdef HAVE_QCA2
432     putPrivmsg(serverEncode(target), encMsg, network()->cipher(target));
433 #else
434     putPrivmsg(serverEncode(target), encMsg);
435 #endif
436 }
437
438
439 void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QString &msg)
440 {
441     Q_UNUSED(bufferInfo)
442     QString nick = msg.section(' ', 0, 0);
443     emit putCmd("NICK", serverEncode(nick));
444 }
445
446
447 void CoreUserInputHandler::handleNotice(const BufferInfo &bufferInfo, const QString &msg)
448 {
449     QString bufferName = msg.section(' ', 0, 0);
450     QString payload = msg.section(' ', 1);
451     QList<QByteArray> params;
452     params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), payload);
453     emit putCmd("NOTICE", params);
454     emit displayMsg(Message::Notice, bufferName, payload, network()->myNick(), Message::Self);
455 }
456
457
458
459 void CoreUserInputHandler::handleOper(const BufferInfo &bufferInfo, const QString &msg)
460 {
461     Q_UNUSED(bufferInfo)
462     emit putRawLine(serverEncode(QString("OPER %1").arg(msg)));
463 }
464
465
466 void CoreUserInputHandler::handlePart(const BufferInfo &bufferInfo, const QString &msg)
467 {
468     QList<QByteArray> params;
469     QString partReason;
470
471     // msg might contain either a channel name and/or a reaon, so we have to check if the first word is a known channel
472     QString channelName = msg.section(' ', 0, 0);
473     if (channelName.isEmpty() || !network()->ircChannel(channelName)) {
474         channelName = bufferInfo.bufferName();
475         partReason = msg;
476     }
477     else {
478         partReason = msg.mid(channelName.length() + 1);
479     }
480
481     if (partReason.isEmpty())
482         partReason = network()->identityPtr()->partReason();
483
484     params << serverEncode(channelName) << channelEncode(bufferInfo.bufferName(), partReason);
485     emit putCmd("PART", params);
486 }
487
488
489 void CoreUserInputHandler::handlePing(const BufferInfo &bufferInfo, const QString &msg)
490 {
491     Q_UNUSED(bufferInfo)
492
493     QString param = msg;
494     if (param.isEmpty())
495         param = QTime::currentTime().toString("hh:mm:ss.zzz");
496
497     putCmd("PING", serverEncode(param));
498 }
499
500
501 // TODO: implement queries
502 void CoreUserInputHandler::handleQuery(const BufferInfo &bufferInfo, const QString &msg)
503 {
504     Q_UNUSED(bufferInfo)
505     QString target = msg.section(' ', 0, 0);
506     QString message = msg.section(' ', 1);
507     if (message.isEmpty())
508         emit displayMsg(Message::Server, BufferInfo::QueryBuffer, target, tr("Starting query with %1").arg(target), network()->myNick(), Message::Self);
509     else
510         emit displayMsg(Message::Plain, BufferInfo::QueryBuffer, target, message, network()->myNick(), Message::Self);
511     handleMsg(bufferInfo, msg);
512 }
513
514
515 void CoreUserInputHandler::handleQuit(const BufferInfo &bufferInfo, const QString &msg)
516 {
517     Q_UNUSED(bufferInfo)
518     network()->disconnectFromIrc(true, msg);
519 }
520
521
522 void CoreUserInputHandler::issueQuit(const QString &reason)
523 {
524     emit putCmd("QUIT", serverEncode(reason));
525 }
526
527
528 void CoreUserInputHandler::handleQuote(const BufferInfo &bufferInfo, const QString &msg)
529 {
530     Q_UNUSED(bufferInfo)
531     emit putRawLine(serverEncode(msg));
532 }
533
534
535 void CoreUserInputHandler::handleSay(const BufferInfo &bufferInfo, const QString &msg)
536 {
537     if (bufferInfo.bufferName().isEmpty())
538         return;  // server buffer
539
540     QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg);
541 #ifdef HAVE_QCA2
542     putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, network()->cipher(bufferInfo.bufferName()));
543 #else
544     putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg);
545 #endif
546     emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self);
547 }
548
549
550 void CoreUserInputHandler::handleSetkey(const BufferInfo &bufferInfo, const QString &msg)
551 {
552 #ifdef HAVE_QCA2
553     if (!bufferInfo.isValid())
554         return;
555
556     if (!Cipher::neededFeaturesAvailable())
557         return;
558
559     QStringList parms = msg.split(' ', QString::SkipEmptyParts);
560
561     if (parms.count() == 1 && !bufferInfo.bufferName().isEmpty())
562         parms.prepend(bufferInfo.bufferName());
563     else if (parms.count() != 2) {
564         emit displayMsg(Message::Info, bufferInfo.bufferName(),
565             tr("[usage] /setkey <nick|channel> <key> sets the encryption key for nick or channel. "
566                "/setkey <key> when in a channel or query buffer sets the key for it."));
567         return;
568     }
569
570     QString target = parms.at(0);
571     QByteArray key = parms.at(1).toLocal8Bit();
572
573     network()->setCipherKey(target, key);
574
575     if (network()->isChannelName(target) && network()->channels().contains(target))
576         qobject_cast<CoreIrcChannel *>(network()->ircChannel(target))->setEncrypted(true);
577     else if (network()->nicks().contains(target))
578         qobject_cast<CoreIrcUser *>(network()->ircUser(target))->setEncrypted(true);
579
580     emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("The key for %1 has been set.").arg(target));
581 #else
582     Q_UNUSED(msg)
583     emit displayMsg(Message::Error, bufferInfo.bufferName(), tr("Error: Setting an encryption key requires Quassel to have been built "
584                                                                 "with support for the Qt Cryptographic Architecture (QCA) library. "
585                                                                 "Contact your distributor about a Quassel package with QCA "
586                                                                 "support, or rebuild Quassel with QCA present."));
587 #endif
588 }
589
590
591 void CoreUserInputHandler::handleShowkey(const BufferInfo &bufferInfo, const QString &msg)
592 {
593 #ifdef HAVE_QCA2
594     if (!bufferInfo.isValid())
595         return;
596
597     if (!Cipher::neededFeaturesAvailable())
598         return;
599
600     QStringList parms = msg.split(' ', QString::SkipEmptyParts);
601
602     if (parms.isEmpty() && !bufferInfo.bufferName().isEmpty())
603         parms.prepend(bufferInfo.bufferName());
604
605     if (parms.isEmpty()) {
606         emit displayMsg(Message::Info, bufferInfo.bufferName(), "",
607             tr("[usage] /showkey <nick|channel> shows the encryption key for nick or channel or just /showkey when in a channel or query."));
608         return;
609     }
610
611     QString target = parms.at(0);
612     QByteArray key = network()->cipherKey(target);
613
614     if (key.isEmpty()) {
615         emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("No key has been set for %1.").arg(target));
616         return;
617     }
618
619     emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("The key for %1 is %2").arg(target).arg(QString(key)));
620
621 #else
622     Q_UNUSED(msg)
623     emit displayMsg(Message::Error, bufferInfo.bufferName(), "", tr("Error: Setting an encryption key requires Quassel to have been built "
624                                                                     "with support for the Qt Cryptographic Architecture (QCA2) library. "
625                                                                     "Contact your distributor about a Quassel package with QCA2 "
626                                                                     "support, or rebuild Quassel with QCA2 present."));
627 #endif
628 }
629
630
631 void CoreUserInputHandler::handleTopic(const BufferInfo &bufferInfo, const QString &msg)
632 {
633     if (bufferInfo.bufferName().isEmpty())
634         return;
635
636     QList<QByteArray> params;
637     params << serverEncode(bufferInfo.bufferName());
638
639     if (!msg.isEmpty()) {
640 #   ifdef HAVE_QCA2
641         params << encrypt(bufferInfo.bufferName(), channelEncode(bufferInfo.bufferName(), msg));
642 #   else
643         params << channelEncode(bufferInfo.bufferName(), msg);
644 #   endif
645     }
646
647     emit putCmd("TOPIC", params);
648 }
649
650
651 void CoreUserInputHandler::handleVoice(const BufferInfo &bufferInfo, const QString &msg)
652 {
653     QStringList nicks = msg.split(' ', QString::SkipEmptyParts);
654     QString m = "+"; for (int i = 0; i < nicks.count(); i++) m += 'v';
655     QStringList params;
656     params << bufferInfo.bufferName() << m << nicks;
657     emit putCmd("MODE", serverEncode(params));
658 }
659
660
661 void CoreUserInputHandler::handleWait(const BufferInfo &bufferInfo, const QString &msg)
662 {
663     int splitPos = msg.indexOf(';');
664     if (splitPos <= 0)
665         return;
666
667     bool ok;
668     int delay = msg.left(splitPos).trimmed().toInt(&ok);
669     if (!ok)
670         return;
671
672     delay *= 1000;
673
674     QString command = msg.mid(splitPos + 1).trimmed();
675     if (command.isEmpty())
676         return;
677
678     _delayedCommands[startTimer(delay)] = Command(bufferInfo, command);
679 }
680
681
682 void CoreUserInputHandler::handleWho(const BufferInfo &bufferInfo, const QString &msg)
683 {
684     Q_UNUSED(bufferInfo)
685     emit putCmd("WHO", serverEncode(msg.split(' ')));
686 }
687
688
689 void CoreUserInputHandler::handleWhois(const BufferInfo &bufferInfo, const QString &msg)
690 {
691     Q_UNUSED(bufferInfo)
692     emit putCmd("WHOIS", serverEncode(msg.split(' ')));
693 }
694
695
696 void CoreUserInputHandler::handleWhowas(const BufferInfo &bufferInfo, const QString &msg)
697 {
698     Q_UNUSED(bufferInfo)
699     emit putCmd("WHOWAS", serverEncode(msg.split(' ')));
700 }
701
702
703 void CoreUserInputHandler::defaultHandler(QString cmd, const BufferInfo &bufferInfo, const QString &msg)
704 {
705     Q_UNUSED(bufferInfo);
706     emit putCmd(serverEncode(cmd.toUpper()), serverEncode(msg.split(" ")));
707 }
708
709
710 void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher)
711 {
712     // Encrypted messages need special care. There's no clear relation between cleartext and encrypted message length,
713     // so we can't just compute the maxSplitPos. Instead, we need to loop through the splitpoints until the crypted
714     // version is short enough...
715     // TODO: check out how the various possible encryption methods behave length-wise and make
716     //       this clean by predicting the length of the crypted msg.
717     //       For example, blowfish-ebc seems to create 8-char chunks.
718
719     static const char *cmd = "PRIVMSG";
720     static const char *splitter = " .,-";
721
722     int maxSplitPos = message.count();
723     int splitPos = maxSplitPos;
724     forever {
725         QByteArray crypted = message.left(splitPos);
726         bool isEncrypted = false;
727 #ifdef HAVE_QCA2
728         if (cipher && !message.isEmpty()) {
729             isEncrypted = cipher->encrypt(crypted);
730         }
731 #endif
732         int overrun = lastParamOverrun(cmd, QList<QByteArray>() << target << crypted);
733         if (overrun) {
734             // In case this is not an encrypted msg, we can just cut off at the end
735             if (!isEncrypted)
736                 maxSplitPos = message.count() - overrun;
737
738             splitPos = -1;
739             for (const char *splitChar = splitter; *splitChar != 0; splitChar++) {
740                 splitPos = qMax(splitPos, message.lastIndexOf(*splitChar, maxSplitPos) + 1); // keep split char on old line
741             }
742             if (splitPos <= 0 || splitPos > maxSplitPos)
743                 splitPos = maxSplitPos;
744
745             maxSplitPos = splitPos - 1;
746             if (maxSplitPos <= 0) { // this should never happen, but who knows...
747                 qWarning() << tr("[Error] Could not encrypt your message: %1").arg(message.data());
748                 return;
749             }
750             continue; // we never come back here for !encrypted!
751         }
752
753         // now we have found a valid splitpos (or didn't need to split to begin with)
754         putCmd(cmd, QList<QByteArray>() << target << crypted);
755         if (splitPos < message.count())
756             putPrivmsg(target, message.mid(splitPos), cipher);
757
758         return;
759     }
760 }
761
762
763 // returns 0 if the message will not be chopped by the irc server or number of chopped bytes if message is too long
764 int CoreUserInputHandler::lastParamOverrun(const QString &cmd, const QList<QByteArray> &params)
765 {
766     // the server will pass our message truncated to 512 bytes including CRLF with the following format:
767     // ":prefix COMMAND param0 param1 :lastparam"
768     // where prefix = "nickname!user@host"
769     // that means that the last message can be as long as:
770     // 512 - nicklen - userlen - hostlen - commandlen - sum(param[0]..param[n-1])) - 2 (for CRLF) - 4 (":!@" + 1space between prefix and command) - max(paramcount - 1, 0) (space for simple params) - 2 (space and colon for last param)
771     IrcUser *me = network()->me();
772     int maxLen = 480 - cmd.toAscii().count(); // educated guess in case we don't know us (yet?)
773
774     if (me)
775         maxLen = 512 - serverEncode(me->nick()).count() - serverEncode(me->user()).count() - serverEncode(me->host()).count() - cmd.toAscii().count() - 6;
776
777     if (!params.isEmpty()) {
778         for (int i = 0; i < params.count() - 1; i++) {
779             maxLen -= (params[i].count() + 1);
780         }
781         maxLen -= 2; // " :" last param separator;
782
783         if (params.last().count() > maxLen) {
784             return params.last().count() - maxLen;
785         }
786         else {
787             return 0;
788         }
789     }
790     else {
791         return 0;
792     }
793 }
794
795
796 #ifdef HAVE_QCA2
797 QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray &message_, bool *didEncrypt) const
798 {
799     if (didEncrypt)
800         *didEncrypt = false;
801
802     if (message_.isEmpty())
803         return message_;
804
805     if (!Cipher::neededFeaturesAvailable())
806         return message_;
807
808     Cipher *cipher = network()->cipher(target);
809     if (!cipher)
810         return message_;
811
812     QByteArray message = message_;
813     bool result = cipher->encrypt(message);
814     if (didEncrypt)
815         *didEncrypt = result;
816
817     return message;
818 }
819
820
821 #endif
822
823 void CoreUserInputHandler::timerEvent(QTimerEvent *event)
824 {
825     if (!_delayedCommands.contains(event->timerId())) {
826         QObject::timerEvent(event);
827         return;
828     }
829     BufferInfo bufferInfo = _delayedCommands[event->timerId()].bufferInfo;
830     QString rawCommand = _delayedCommands[event->timerId()].command;
831     _delayedCommands.remove(event->timerId());
832     event->accept();
833
834     // the stored command might be the result of an alias expansion, so we need to split it up again
835     QStringList commands = rawCommand.split(QRegExp("; ?"));
836     foreach(QString command, commands) {
837         handleUserInput(bufferInfo, command);
838     }
839 }