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