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