1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com>
5     SPDX-FileCopyrightText: 2005 Ismail Donmez <ismail@kde.org>
6     SPDX-FileCopyrightText: 2005 Peter Simonsson <psn@linux.se>
7     SPDX-FileCopyrightText: 2005 John Tapsell <johnflux@gmail.com>
8     SPDX-FileCopyrightText: 2005-2009 Eike Hein <hein@kde.org>
9 */
10 
11 #include "outputfilter.h"
12 
13 #include "ircview.h"
14 #include "application.h"
15 #include "mainwindow.h"
16 #include "channel.h"
17 #include "awaymanager.h"
18 #include "ignore.h"
19 #include "server.h"
20 #include "scriptlauncher.h"
21 #include "irccharsets.h"
22 #include "query.h"
23 #include "viewcontainer.h"
24 #include "outputfilterresolvejob.h"
25 #include "konversation_log.h"
26 #ifdef HAVE_QCA2
27 #include "cipher.h"
28 #endif
29 
30 #include <KPasswordDialog>
31 #include <KMessageBox>
32 #include <kxmlgui_version.h>
33 
34 #include <QStringList>
35 #include <QFile>
36 #include <QMetaMethod>
37 #include <QRegularExpression>
38 #include <QTextCodec>
39 #include <QByteArray>
40 #include <QTextStream>
41 #include <QTextDocument>
42 #include <QDebug>
43 
44 QDebug operator<<(QDebug d, QTextDocument* document);
45 
46 namespace Konversation
47 {
48     QSet<QString> OutputFilter::m_commands;
49 
fillCommandList()50     void OutputFilter::fillCommandList()
51     {
52         if (!m_commands.isEmpty())
53             return;
54 
55         QString methodSignature;
56 
57         for (int i = OutputFilter::staticMetaObject.methodOffset();
58             i < OutputFilter::staticMetaObject.methodCount(); ++i)
59         {
60             methodSignature = QString::fromLatin1(OutputFilter::staticMetaObject.method(i).methodSignature());
61 
62             if (methodSignature.startsWith(QLatin1String("command_")))
63                 m_commands << methodSignature.mid(8).section(QLatin1Char('('), 0, 0).toLower();
64         }
65     }
66 
OutputFilter(Server * server)67     OutputFilter::OutputFilter(Server* server)
68         : QObject(server)
69     {
70         m_server = server;
71 
72         fillCommandList();
73     }
74 
~OutputFilter()75     OutputFilter::~OutputFilter()
76     {
77     }
78 
79     // replace all aliases in the string and return true if anything got replaced at all
replaceAliases(QString & line,ChatWindow * context)80     bool OutputFilter::replaceAliases(QString& line, ChatWindow* context)
81     {
82         const QStringList aliasList = Preferences::self()->aliasList();
83 
84         for(const QString& alias : aliasList) {
85             // cut alias pattern from definition
86             QString aliasPattern(alias.section(QLatin1Char(' '), 0, 0));
87 
88             // cut first word from command line, so we do not wrongly find an alias
89             // that starts with the same letters, like /m would override /me
90             QString lineStart = line.section(QLatin1Char(' '), 0, 0);
91 
92             // pattern found?
93             if (lineStart == Preferences::self()->commandChar() + aliasPattern)
94             {
95                 QString aliasReplace = alias.section(QLatin1Char(' '),1);
96 
97                 if (context)
98                     aliasReplace = context->getServer()->parseWildcards(aliasReplace, context);
99 
100                 if (!alias.contains(QLatin1String("%p")))
101                     aliasReplace.append(QLatin1Char(' ') + line.section(QLatin1Char(' '), 1));
102 
103                 // protect "%%"
104                 aliasReplace.replace(QStringLiteral("%%"),QStringLiteral("%\x01"));
105                 // replace %p placeholder with rest of line
106                 aliasReplace.replace(QStringLiteral("%p"), line.section(QLatin1Char(' '), 1));
107                 // restore "%<1>" as "%%"
108                 aliasReplace.replace(QStringLiteral("%\x01"),QStringLiteral("%%"));
109                 // modify line
110                 line=aliasReplace;
111                 // return "replaced"
112                 return true;
113             }
114         }
115 
116         return false;
117     }
118 
splitForEncoding(const QString & destination,const QString & inputLine,int max,int segments)119     QStringList OutputFilter::splitForEncoding(const QString& destination, const QString& inputLine,
120                                                int max, int segments)
121     {
122         int sublen = 0; //The encoded length since the last split
123         int charLength = 0; //the length of this char
124         int lastBreakPoint = 0;
125 
126         //FIXME should we run this through the encoder first, checking with "canEncode"?
127         QString text = inputLine; // the text we'll send, currently in Unicode
128         QStringList finals; // The strings we're going to output
129 
130         QString channelCodecName = Preferences::channelEncoding(m_server->getDisplayName(), destination);
131         //Get the codec we're supposed to use. This must not fail. (not verified)
132         QTextCodec* codec;
133 
134         // I copied this bit straight out of Server::send
135         if (channelCodecName.isEmpty())
136         {
137             codec = m_server->getIdentity()->getCodec();
138         }
139         else
140         {
141             codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName);
142         }
143 
144         Q_ASSERT(codec);
145         int index = 0;
146 
147         while (index < text.length() && (segments == -1 || finals.size() < segments-1))
148         {
149             // The most important bit - turn the current char into a QCString so we can measure it
150             QByteArray ch = codec->fromUnicode(QString(text[index]));
151             charLength = ch.length();
152 
153             // If adding this char puts us over the limit:
154             if (charLength + sublen > max)
155             {
156                 if (lastBreakPoint != 0)
157                 {
158                     finals.append(text.left(lastBreakPoint + 1));
159                     text = text.mid(lastBreakPoint + 1);
160                 }
161                 else
162                 {
163                     finals.append(text.left(index));
164                     text = text.mid(index);
165                 }
166 
167                 lastBreakPoint = 0;
168                 sublen = 0;
169                 index = 0;
170             }
171             else if (text[index].isSpace() || text[index].isPunct())
172             {
173                 lastBreakPoint = index;
174             }
175 
176             ++index;
177             sublen += charLength;
178         }
179 
180         if (!text.isEmpty())
181         {
182             finals.append(text);
183         }
184 
185         return finals;
186     }
187 
checkForEncodingConflict(QString * line,const QString & target)188     bool OutputFilter::checkForEncodingConflict(QString *line, const QString& target)
189     {
190         QTextCodec* codec = nullptr;
191         QString encoding;
192         QString oldLine(*line);
193         if(m_server->getServerGroup())
194             encoding = Preferences::channelEncoding(m_server->getServerGroup()->id(), target);
195         else
196             encoding = Preferences::channelEncoding(m_server->getDisplayName(), target);
197 
198         if(encoding.isEmpty())
199             codec = m_server->getIdentity()->getCodec();
200         else
201             codec = Konversation::IRCCharsets::self()->codecForName(encoding);
202 
203         QTextCodec::ConverterState state;
204 
205         QString newLine;
206         if(codec)
207             newLine = codec->toUnicode(codec->fromUnicode(oldLine.constData(),oldLine.length(),&state));
208 
209         if(!newLine.isEmpty() && state.invalidChars)
210         {
211             int ret = KMessageBox::Continue;
212 
213             ret = KMessageBox::warningContinueCancel(m_server->getViewContainer()->getWindow(),
214             i18n("The message you are sending includes characters "
215                  "that do not exist in your current encoding. If "
216                  "you choose to continue anyway those characters "
217                  "will be replaced by a '?'."),
218             i18n("Encoding Conflict Warning"),
219             KStandardGuiItem::cont(),
220             KStandardGuiItem::cancel(),
221             QStringLiteral("WarnEncodingConflict"));
222 
223             *line = newLine; //set so we show it as it's sent
224             if (ret != KMessageBox::Continue) return true;
225         }
226 
227         return false;
228     }
229 
parse(const QString & myNick,const QString & originalLine,const QString & destination,ChatWindow * inputContext)230     OutputFilterResult OutputFilter::parse(const QString& myNick, const QString& originalLine,
231         const QString& destination, ChatWindow* inputContext)
232     {
233         OutputFilterInput input;
234         input.myNick = myNick;
235         input.destination = destination;
236         input.context = inputContext;
237 
238         OutputFilterResult result;
239 
240         QString inputLine(originalLine);
241 
242         if (inputLine.isEmpty() || inputLine == QLatin1Char('\n') || checkForEncodingConflict(&inputLine, destination))
243             return result;
244 
245         //Protect against nickserv auth being sent as a message on the off chance
246         // someone didn't notice leading spaces
247         {
248             QString testNickServ(inputLine.trimmed());
249             if(testNickServ.startsWith(Preferences::self()->commandChar() + QLatin1String("nickserv"), Qt::CaseInsensitive)
250               || testNickServ.startsWith(Preferences::self()->commandChar() + QLatin1String("ns"), Qt::CaseInsensitive))
251             {
252                     inputLine = testNickServ;
253             }
254         }
255 
256         //perform variable expansion according to prefs
257         inputLine = Konversation::doVarExpansion(inputLine);
258 
259         QString line = inputLine.toLower();
260 
261         // Convert double command chars at the beginning to single ones
262         if(line.startsWith(Preferences::self()->commandChar() + Preferences::self()->commandChar()) && !destination.isEmpty())
263         {
264             inputLine.remove(0, 1);
265             goto BYPASS_COMMAND_PARSING;
266         }
267         // Server command?
268         else if (line.startsWith(Preferences::self()->commandChar()))
269         {
270             const QString command = inputLine.section(QLatin1Char(' '), 0, 0).mid(1).toLower();
271             input.parameter = inputLine.section(QLatin1Char(' '), 1);
272 
273             if (command != QLatin1String("topic"))
274                 input.parameter = input.parameter.trimmed();
275 
276             if (supportedCommands().contains(command))
277             {
278                 QString methodSignature(QLatin1String("command_") + command);
279 
280                 QMetaObject::invokeMethod(this,
281                     methodSignature.toLatin1().constData(),
282                     Qt::DirectConnection,
283                     Q_RETURN_ARG(OutputFilterResult, result),
284                     Q_ARG(OutputFilterInput, input));
285             }
286             // Forward unknown commands to server
287             else
288             {
289                 result.toServer = inputLine.mid(1);
290                 result.type = Message;
291             }
292         }
293         // Ordinary message to channel/query?
294         else if (!destination.isEmpty())
295         {
296             BYPASS_COMMAND_PARSING:
297 
298             const QStringList outputList = splitForEncoding(destination, inputLine,
299                                                             m_server->getPreLength(QStringLiteral("PRIVMSG"), destination));
300 
301             if (outputList.count() > 1)
302             {
303                 result.output.clear();
304                 result.outputList = outputList;
305                 result.toServerList.reserve(outputList.size());
306                 for (const QString& out : outputList) {
307                     result.toServerList += QLatin1String("PRIVMSG ") + destination + QLatin1String(" :") + out;
308                 }
309             }
310             else
311             {
312                 result.output = inputLine;
313                 result.toServer = QLatin1String("PRIVMSG ") + destination + QLatin1String(" :") + inputLine;
314             }
315 
316             result.type = Message;
317         }
318         // Eveything else goes to the server unchanged
319         else
320         {
321             result.toServer = inputLine;
322             result.output = inputLine;
323             result.typeString = i18n("Raw");
324             result.type = Program;
325         }
326 
327         return result;
328     }
329 
command_op(const OutputFilterInput & input)330     OutputFilterResult OutputFilter::command_op(const OutputFilterInput& input)
331     {
332         return changeMode(input.parameter, input.destination, 'o', '+');
333     }
334 
command_deop(const OutputFilterInput & input)335     OutputFilterResult OutputFilter::command_deop(const OutputFilterInput& input)
336     {
337         return changeMode(addNickToEmptyNickList(input.myNick, input.parameter),
338                           input.destination, 'o', '-');
339     }
340 
command_hop(const OutputFilterInput & input)341     OutputFilterResult OutputFilter::command_hop(const OutputFilterInput& input)
342     {
343         return changeMode(input.parameter, input.destination, 'h', '+');
344     }
345 
command_dehop(const OutputFilterInput & input)346     OutputFilterResult OutputFilter::command_dehop(const OutputFilterInput& input)
347     {
348         return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination,
349                           'h', '-');
350     }
351 
command_voice(const OutputFilterInput & input)352     OutputFilterResult OutputFilter::command_voice(const OutputFilterInput& input)
353     {
354         return changeMode(input.parameter, input.destination, 'v', '+');
355     }
356 
command_unvoice(const OutputFilterInput & input)357     OutputFilterResult OutputFilter::command_unvoice(const OutputFilterInput& input)
358     {
359         return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination,
360                           'v', '-');
361     }
362 
command_devoice(const OutputFilterInput & input)363     OutputFilterResult OutputFilter::command_devoice(const OutputFilterInput& input)
364     {
365         return command_unvoice(input);
366     }
367 
command_join(const OutputFilterInput & _input)368     OutputFilterResult OutputFilter::command_join(const OutputFilterInput& _input)
369     {
370         OutputFilterInput input(_input);
371         OutputFilterResult result;
372 
373         if (input.parameter.contains(QLatin1Char(','))) // Protect against #foo,0 tricks
374             input.parameter.remove(QStringLiteral(",0"));
375         //else if(channelName == "0") // FIXME IRC RFC 2812 section 3.2.1
376 
377         if (input.parameter.isEmpty())
378         {
379             if (input.destination.isEmpty() || !isAChannel(input.destination))
380                 return usage(i18n("Usage: %1JOIN <channel> [password]",
381                                   Preferences::self()->commandChar()));
382 
383             input.parameter = input.destination;
384         }
385         else if (!isAChannel(input.parameter))
386             input.parameter = QLatin1Char('#') + input.parameter.trimmed();
387 
388         Channel* channel = m_server->getChannelByName(input.parameter);
389 
390         if (channel)
391         {
392             // Note that this relies on the channels-flush-nicklists-on-disconnect behavior.
393             if (!channel->numberOfNicks())
394                 result.toServer = QLatin1String("JOIN ") + input.parameter;
395 
396             if (channel->isJoined())
397                 Q_EMIT showView(channel);
398         }
399         else
400             result.toServer = QLatin1String("JOIN ") + input.parameter;
401 
402         return result;
403     }
404 
command_j(const OutputFilterInput & input)405     OutputFilterResult OutputFilter::command_j(const OutputFilterInput& input)
406     {
407         return command_join(input);
408     }
409 
command_kick(const OutputFilterInput & input)410     OutputFilterResult OutputFilter::command_kick(const OutputFilterInput& input)
411     {
412         OutputFilterResult result;
413 
414         if (isAChannel(input.destination))
415         {
416             // get nick to kick
417             const QString victim = input.parameter.left(input.parameter.indexOf(QLatin1Char(' ')));
418 
419             if (victim.isEmpty())
420                 result = usage(i18n("Usage: %1KICK <nick> [reason]", Preferences::self()->commandChar()));
421             else
422             {
423                 // get kick reason (if any)
424                 QString reason = input.parameter.mid(victim.length() + 1);
425 
426                 // if no reason given, take default reason
427                 if (reason.isEmpty())
428                     reason = m_server->getIdentity()->getKickReason();
429 
430                 result.toServer = QLatin1String("KICK ") + input.destination + QLatin1Char(' ') + victim + QLatin1String(" :") + reason;
431             }
432         }
433         else
434             result = error(i18n("%1KICK only works from within channels.",
435                                 Preferences::self()->commandChar()));
436 
437         return result;
438     }
439 
command_part(const OutputFilterInput & input)440     OutputFilterResult OutputFilter::command_part(const OutputFilterInput& input)
441     {
442         OutputFilterResult result;
443 
444         // No parameter, try default part message
445         if (input.parameter.isEmpty())
446         {
447             // But only if we actually are in a channel
448             if (isAChannel(input.destination))
449                 result.toServer = QLatin1String("PART ") + input.destination + QLatin1String(" :") +
450                                   m_server->getIdentity()->getPartReason();
451             else if (m_server->getQueryByName(input.destination))
452                 m_server->closeQuery(input.destination);
453             else
454                 result = error(i18n("%1PART and %1LEAVE without parameters only work from within a "
455                                     "channel or a query.", Preferences::self()->commandChar()));
456         }
457         else
458         {
459             // part a given channel
460             if (isAChannel(input.parameter))
461             {
462                 // get channel name
463                 const QString channel = input.parameter.left(input.parameter.indexOf(QLatin1Char(' ')));
464                 // get part reason (if any)
465                 QString reason = input.parameter.mid(channel.length() + 1);
466 
467                 // if no reason given, take default reason
468                 if (reason.isEmpty())
469                     reason = m_server->getIdentity()->getPartReason();
470 
471                 result.toServer = QLatin1String("PART ") + channel + QLatin1String(" :") + reason;
472             }
473             // part this channel with a given reason
474             else
475             {
476                 if (isAChannel(input.destination))
477                     result.toServer = QLatin1String("PART ") + input.destination + QLatin1String(" :") + input.parameter;
478                 else
479                     result = error(i18n("%1PART without channel name only works from within a channel.",
480                                         Preferences::self()->commandChar()));
481             }
482         }
483 
484         return result;
485     }
486 
command_leave(const OutputFilterInput & input)487     OutputFilterResult OutputFilter::command_leave(const OutputFilterInput& input)
488     {
489         return command_part(input);
490     }
491 
command_topic(const OutputFilterInput & input)492     OutputFilterResult OutputFilter::command_topic(const OutputFilterInput& input)
493     {
494         OutputFilterResult result;
495 
496         // No parameter, try to get current topic
497         if (input.parameter.isEmpty())
498         {
499             // But only if we actually are in a channel
500             if (isAChannel(input.destination))
501                 result.toServer = QLatin1String("TOPIC ") + input.destination;
502             else
503                 result = error(i18n("%1TOPIC without parameters only works from within a channel.",
504                                     Preferences::self()->commandChar()));
505         }
506         else
507         {
508             // retrieve or set topic of a given channel
509             if (isAChannel(input.parameter))
510             {
511                 // get channel name
512                 const QString channel = input.parameter.left(input.parameter.indexOf(QLatin1Char(' ')));
513                 // get topic (if any)
514                 QString topic = input.parameter.mid(channel.length()+1);
515                 // if no topic given, retrieve topic
516                 if (topic.isEmpty())
517                     m_server->requestTopic(channel);
518 
519                 // otherwise set topic there
520                 else
521                 {
522                     result.toServer = QLatin1String("TOPIC ") + channel + QLatin1String(" :");
523                     //If we get passed a ^A as a topic its a sign we should clear the topic.
524                     //Used to be a \n, but those get smashed by QStringList::split and readded later
525                     //Now is not the time to fight with that. FIXME
526                     //If anyone out there *can* actually set the topic to a single ^A, now they have to
527                     //specify it twice to get one.
528                     if (topic == QLatin1String("\x01\x01"))
529                         result.toServer += QLatin1Char('\x01');
530                     else if (topic != QLatin1String("\x01"))
531                         result.toServer += topic;
532                 }
533             }
534             // set this channel's topic
535             else
536             {
537                 if (isAChannel(input.destination))
538                     result.toServer = QLatin1String("TOPIC ") + input.destination + QLatin1String(" :") + input.parameter;
539                 else
540                     result = error(i18n("%1TOPIC without channel name only works from within a channel.",
541                                         Preferences::self()->commandChar()));
542             }
543         }
544 
545         return result;
546     }
547 
command_away(const OutputFilterInput & input)548     OutputFilterResult OutputFilter::command_away(const OutputFilterInput& input)
549     {
550         if (input.parameter.isEmpty() && m_server->isAway())
551             m_server->requestUnaway();
552         else
553             m_server->requestAway(input.parameter);
554 
555         return OutputFilterResult();
556     }
557 
command_unaway(const OutputFilterInput &)558     OutputFilterResult OutputFilter::command_unaway(const OutputFilterInput& /* input */)
559     {
560         m_server->requestUnaway();
561 
562         return OutputFilterResult();
563     }
564 
command_back(const OutputFilterInput & input)565     OutputFilterResult OutputFilter::command_back(const OutputFilterInput& input)
566     {
567         return command_unaway(input);
568     }
569 
command_aaway(const OutputFilterInput & input)570     OutputFilterResult OutputFilter::command_aaway(const OutputFilterInput& input)
571     {
572         Application::instance()->getAwayManager()->requestAllAway(input.parameter);
573 
574         return OutputFilterResult();
575     }
576 
command_aunaway(const OutputFilterInput &)577     OutputFilterResult OutputFilter::command_aunaway(const OutputFilterInput& /* input */)
578     {
579         Application::instance()->getAwayManager()->requestAllUnaway();
580 
581         return OutputFilterResult();
582     }
583 
command_aback(const OutputFilterInput & input)584     OutputFilterResult OutputFilter::command_aback(const OutputFilterInput& input)
585     {
586         return command_aunaway(input);
587     }
588 
command_names(const OutputFilterInput & input)589     OutputFilterResult OutputFilter::command_names(const OutputFilterInput& input)
590     {
591         OutputFilterResult result;
592 
593         result.toServer = QStringLiteral("NAMES ");
594 
595         if (input.parameter.isNull())
596             result = error(i18n("%1NAMES with no target may disconnect you from the server. Specify "
597                                 "'*' if you really want this.", Preferences::self()->commandChar()));
598         else if (input.parameter != QLatin1Char('*'))
599             result.toServer.append(input.parameter);
600 
601         return result;
602     }
603 
command_close(const OutputFilterInput & _input)604     OutputFilterResult OutputFilter::command_close(const OutputFilterInput& _input)
605     {
606         OutputFilterInput input(_input);
607         OutputFilterResult result;
608 
609         if (input.parameter.isEmpty())
610             input.parameter = input.destination;
611 
612         if (isAChannel(input.parameter) && m_server->getChannelByName(input.parameter))
613             m_server->getChannelByName(input.parameter)->closeYourself(false);
614         else if (m_server->getQueryByName(input.parameter))
615             m_server->getQueryByName(input.parameter)->closeYourself(false);
616         else if (input.parameter.isEmpty()) // this can only mean one thing.. we're in the Server tab
617             m_server->closeYourself(false);
618         else
619             result = usage(i18n("Usage: %1close [window] closes the named channel or query tab, "
620                                 "or the current tab if none specified.",
621                                 Preferences::self()->commandChar()));
622 
623         return result;
624     }
625 
command_reconnect(const OutputFilterInput & input)626     OutputFilterResult OutputFilter::command_reconnect(const OutputFilterInput& input)
627     {
628         Q_EMIT reconnectServer(input.parameter);
629 
630         return OutputFilterResult();
631     }
632 
command_disconnect(const OutputFilterInput & input)633     OutputFilterResult OutputFilter::command_disconnect(const OutputFilterInput& input)
634     {
635         Q_EMIT disconnectServer(input.parameter);
636 
637         return OutputFilterResult();
638     }
639 
command_quit(const OutputFilterInput & input)640     OutputFilterResult OutputFilter::command_quit(const OutputFilterInput& input)
641     {
642         Q_EMIT disconnectServer(input.parameter);
643 
644         return OutputFilterResult();
645     }
646 
command_notice(const OutputFilterInput & input)647     OutputFilterResult OutputFilter::command_notice(const OutputFilterInput& input)
648     {
649         OutputFilterResult result;
650 
651         const QString recipient = input.parameter.left(input.parameter.indexOf(QLatin1Char(' ')));
652         QString message = input.parameter.mid(recipient.length()+1);
653 
654         if (input.parameter.isEmpty() || message.isEmpty())
655             result = usage(i18n("Usage: %1NOTICE <recipient> <message>",
656                                 Preferences::self()->commandChar()));
657         else
658         {
659             result.typeString = i18n("Notice");
660             result.toServer = QLatin1String("NOTICE ") + recipient + QLatin1String(" :") + message;
661             result.output=i18nc("%1 is the message, %2 the recipient nickname",
662                                 "Sending notice \"%1\" to %2.", message, recipient);
663             result.type = Program;
664         }
665 
666         return result;
667     }
668 
command_me(const OutputFilterInput & input)669     OutputFilterResult OutputFilter::command_me(const OutputFilterInput& input)
670     {
671         OutputFilterResult result;
672 
673         if (input.destination.isEmpty())
674             return error(i18n("%1ME only works from a channel, query or DCC chat tab.", Preferences::self()->commandChar()));
675         else if (input.parameter.isEmpty())
676             return usage(i18n("Usage: %1ME text", Preferences::self()->commandChar()));
677 
678         QString command = QStringLiteral("PRIVMSGACTION \x01\x01");
679 
680         QStringList outputList = splitForEncoding(input.destination, input.parameter,
681                                                   m_server->getPreLength(command, input.destination), 2);
682 
683         if (outputList.count() > 1)
684         {
685             command = QStringLiteral("PRIVMSG");
686 
687             outputList += splitForEncoding(input.destination, outputList.at(1),
688                                            m_server->getPreLength(command, input.destination));
689 
690             outputList.removeAt(1);
691 
692             result.output.clear();
693             result.outputList = outputList;
694 
695             for (int i = 0; i < outputList.count(); ++i)
696             {
697                 if (i == 0)
698                     result.toServerList += QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :\x01""ACTION ") + outputList.at(i) + QLatin1Char('\x01');
699                 else
700                     result.toServerList += QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :") + outputList.at(i);
701             }
702         }
703         else
704         {
705             result.output = input.parameter;
706             result.toServer = QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :\x01""ACTION ") +
707                               input.parameter + QLatin1Char('\x01');
708         }
709 
710         result.type = Action;
711 
712         return result;
713     }
714 
command_msg(const OutputFilterInput & input)715     OutputFilterResult OutputFilter::command_msg(const OutputFilterInput& input)
716     {
717         return handleMsg(input.parameter, false);
718     }
719 
command_m(const OutputFilterInput & input)720     OutputFilterResult OutputFilter::command_m(const OutputFilterInput& input)
721     {
722         return command_msg(input);
723     }
724 
command_query(const OutputFilterInput & input)725     OutputFilterResult OutputFilter::command_query(const OutputFilterInput& input)
726     {
727         return handleMsg(input.parameter, true);
728     }
729 
handleMsg(const QString & parameter,bool commandIsQuery)730     OutputFilterResult OutputFilter::handleMsg(const QString& parameter, bool commandIsQuery)
731     {
732         OutputFilterResult result;
733 
734         const QString recipient = parameter.section(QLatin1Char(' '), 0, 0, QString::SectionSkipEmpty);
735         const QString message = parameter.section(QLatin1Char(' '), 1);
736         QString output;
737 
738         bool recipientIsAChannel = false;
739 
740         if (recipient.isEmpty())
741             return error(i18n("You need to specify a recipient."));
742         else
743             recipientIsAChannel = m_server->isAChannel(recipient);
744 
745         if (commandIsQuery && recipientIsAChannel)
746             return error(i18n("You cannot open queries to channels."));
747 
748         if (message.trimmed().isEmpty())
749         {
750             // Empty result - we don't want to send any message to the server.
751             if (!commandIsQuery)
752                 return error(i18n("You need to specify a message."));
753         }
754         else
755         {
756             output = message;
757 
758             if (message.startsWith(Preferences::self()->commandChar() + QLatin1String("me")))
759                 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""ACTION ") +
760                                    message.mid(4) + QLatin1Char('\x01');
761             else
762                 result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :") + message;
763         }
764 
765         // If this is a /query, always open a query window.
766         // Treat "/msg nick" as "/query nick".
767         if (commandIsQuery || output.isEmpty())
768         {
769             // Note we have to be a bit careful here.
770             // We only want to create ('obtain') a new nickinfo if we have done /query
771             // or "/msg nick".  Not "/msg nick message".
772             NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient);
773             ::Query* query = m_server->addQuery(nickInfo, true /*we initiated*/);
774 
775             // Force focus if the user did not specify any message.
776             if (output.isEmpty() && Preferences::self()->focusNewQueries()) Q_EMIT showView(query);
777         }
778 
779         // Result should be completely empty;
780         if (output.isEmpty()) return result;
781 
782         //FIXME: Don't do below line if query is focused.
783         result.output = output;
784         result.typeString = recipient;
785         result.type = PrivateMessage;
786 
787         return result;
788     }
789 
command_smsg(const OutputFilterInput & input)790     OutputFilterResult OutputFilter::command_smsg(const OutputFilterInput& input)
791     {
792         OutputFilterResult result;
793 
794         const QString recipient = input.parameter.left(input.parameter.indexOf(QLatin1Char(' ')));
795         QString message = input.parameter.mid(recipient.length() + 1);
796 
797         if (message.startsWith(Preferences::self()->commandChar() + QLatin1String("me")))
798             result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""ACTION ") +
799                               message.mid(4) + QLatin1Char('\x01');
800         else
801             result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :") + message;
802 
803         return result;
804     }
805 
command_ping(const OutputFilterInput & input)806     OutputFilterResult OutputFilter::command_ping(const OutputFilterInput& input)
807     {
808         return handleCtcp(input.parameter.section(QLatin1Char(' '), 0, 0) + QLatin1String(" ping"));
809     }
810 
command_ctcp(const OutputFilterInput & input)811     OutputFilterResult OutputFilter::command_ctcp(const OutputFilterInput& input)
812     {
813         return handleCtcp(input.parameter);
814     }
815 
handleCtcp(const QString & parameter)816     OutputFilterResult OutputFilter::handleCtcp(const QString& parameter)
817     {
818         OutputFilterResult result;
819                                                   // who is the recipient?
820         const QString recipient = parameter.section(QLatin1Char(' '), 0, 0);
821                                                   // what is the first word of the ctcp?
822         const QString request = parameter.section(QLatin1Char(' '), 1, 1, QString::SectionSkipEmpty).toUpper();
823                                                   // what is the complete ctcp command?
824         const QString message = parameter.section(QLatin1Char(' '), 2, 0xffffff, QString::SectionSkipEmpty);
825 
826         QString out = request;
827 
828         if (!message.isEmpty())
829             out+= QLatin1Char(' ') + message;
830 
831         if (request == QLatin1String("PING"))
832         {
833             unsigned int timeT = QDateTime::currentDateTime().toSecsSinceEpoch();
834             result.toServer = QStringLiteral("PRIVMSG %1 :\x01PING %2\x01").arg(recipient).arg(timeT);
835             result.output = i18n("Sending CTCP-%1 request to %2.", QStringLiteral("PING"), recipient);
836         }
837         else
838         {
839             result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01") + out + QLatin1Char('\x01');
840             result.output = i18n("Sending CTCP-%1 request to %2.", out, recipient);
841         }
842 
843         result.typeString = i18n("CTCP");
844         result.type = Program;
845 
846         return result;
847     }
848 
command_ame(const OutputFilterInput & input)849     OutputFilterResult OutputFilter::command_ame(const OutputFilterInput& input)
850     {
851         if (input.parameter.isEmpty())
852             return usage(i18n("Usage: %1AME [-LOCAL] text", Preferences::self()->commandChar()));
853 
854         if (isParameter(QStringLiteral("local"), input.parameter.section(QLatin1Char(' '), 0, 0)))
855             m_server->sendToAllChannelsAndQueries(Preferences::self()->commandChar() + QLatin1String("me ") + input.parameter.section(QLatin1Char(' '), 1));
856         else
857             Q_EMIT multiServerCommand(QStringLiteral("me"), input.parameter);
858 
859         return OutputFilterResult();
860     }
861 
command_amsg(const OutputFilterInput & input)862     OutputFilterResult OutputFilter::command_amsg(const OutputFilterInput& input)
863     {
864         if (input.parameter.isEmpty())
865             return usage(i18n("Usage: %1AMSG [-LOCAL] text", Preferences::self()->commandChar()));
866 
867         if (isParameter(QStringLiteral("local"), input.parameter.section(QLatin1Char(' '), 0, 0)))
868             m_server->sendToAllChannelsAndQueries(input.parameter.section(QLatin1Char(' '), 1));
869         else
870             Q_EMIT multiServerCommand(QStringLiteral("msg"), input.parameter);
871 
872         return OutputFilterResult();
873     }
874 
command_omsg(const OutputFilterInput & input)875     OutputFilterResult OutputFilter::command_omsg(const OutputFilterInput& input)
876     {
877         if (input.parameter.isEmpty())
878             return usage(i18n("Usage: %1OMSG text", Preferences::self()->commandChar()));
879 
880         OutputFilterResult result;
881 
882         result.toServer = QLatin1String("PRIVMSG @") + input.destination + QLatin1String(" :") + input.parameter;
883 
884         return result;
885     }
886 
command_onotice(const OutputFilterInput & input)887     OutputFilterResult OutputFilter::command_onotice(const OutputFilterInput& input)
888     {
889         OutputFilterResult result;
890 
891         if (!input.parameter.isEmpty())
892         {
893             result.toServer = QLatin1String("NOTICE @") + input.destination + QLatin1String(" :") + input.parameter;
894             result.typeString = i18n("Notice");
895             result.type = Program;
896             result.output = i18n("Sending notice \"%1\" to %2.", input.parameter, input.destination);
897         }
898         else
899             result = usage(i18n("Usage: %1ONOTICE text", Preferences::self()->commandChar()));
900 
901         return result;
902     }
903 
command_quote(const OutputFilterInput & input)904     OutputFilterResult OutputFilter::command_quote(const OutputFilterInput& input)
905     {
906         OutputFilterResult result;
907 
908         if (input.parameter.isEmpty())
909             result = usage(i18n("Usage: %1QUOTE command list", Preferences::self()->commandChar()));
910         else
911             result.toServer = input.parameter;
912 
913         return result;
914     }
915 
command_say(const OutputFilterInput & input)916     OutputFilterResult OutputFilter::command_say(const OutputFilterInput& input)
917     {
918         OutputFilterResult result;
919 
920         if (input.parameter.isEmpty())
921             result = usage(i18n("Usage: %1SAY text", Preferences::self()->commandChar()));
922         else
923         {
924             result.toServer = QLatin1String("PRIVMSG ") + input.destination + QLatin1String(" :") + input.parameter;
925             result.output = input.parameter;
926         }
927 
928         return result;
929     }
930 
command_dcc(const OutputFilterInput & _input)931     OutputFilterResult OutputFilter::command_dcc(const OutputFilterInput& _input)
932     {
933         OutputFilterInput input(_input);
934         OutputFilterResult result;
935 
936         qCDebug(KONVERSATION_LOG) << input.parameter;
937         // No parameter, just open DCC panel
938         if (input.parameter.isEmpty())
939         {
940             Q_EMIT addDccPanel();
941         }
942         else
943         {
944             const QStringList parameterList = input.parameter.replace(QLatin1String("\\ "), QLatin1String("%20")).split(QLatin1Char(' '));
945 
946             QString dccType = parameterList[0].toLower();
947 
948             //TODO close should not just refer to the gui-panel, let it close connections
949             if (dccType == QLatin1String("close"))
950             {
951                 Q_EMIT closeDccPanel();
952             }
953             else if (dccType == QLatin1String("send"))
954             {
955                 if (parameterList.count() == 1) // DCC SEND
956                 {
957                     Q_EMIT requestDccSend();
958                 }
959                 else if (parameterList.count() == 2) // DCC SEND <nickname>
960                 {
961                     Q_EMIT requestDccSend(parameterList[1]);
962                 }
963                 else if (parameterList.count() > 2) // DCC SEND <nickname> <file> [file] ...
964                 {
965                     // TODO: make sure this will work:
966                     //output=i18n("Usage: %1DCC SEND nickname [filename] [filename] ...").arg(commandChar);
967                     QUrl fileURL(parameterList[2]);
968 
969                     //We could easily check if the remote file exists, but then we might
970                     //end up asking for creditionals twice, so settle for only checking locally
971                     if (!fileURL.isLocalFile() || QFile::exists(fileURL.path()))
972                     {
973                         Q_EMIT openDccSend(parameterList[1],fileURL);
974                     }
975                     else
976                     {
977                         result = error(i18n("File \"%1\" does not exist.", parameterList[2]));
978                     }
979                 }
980                 else                              // Don't know how this should happen, but ...
981                     result = usage(i18n("Usage: %1DCC [SEND [nickname [filename]]]",
982                                         Preferences::self()->commandChar()));
983             }
984             else if (dccType == QLatin1String("get"))
985             {
986                 //dcc get [nick [file]]
987                 switch (parameterList.count())
988                 {
989                     case 1:
990                         Q_EMIT acceptDccGet(QString(),QString());
991                         break;
992                     case 2:
993                         Q_EMIT acceptDccGet(parameterList.at(1),QString());
994                         break;
995                     case 3:
996                         Q_EMIT acceptDccGet(parameterList.at(1),parameterList.at(2));
997                         break;
998                     default:
999                         result = usage(i18n("Usage: %1DCC [GET [nickname [filename]]]",
1000                                             Preferences::self()->commandChar()));
1001                 }
1002             }
1003             else if (dccType == QLatin1String("chat"))
1004             {
1005                 //dcc chat nick
1006                 switch (parameterList.count())
1007                 {
1008                     case 1:
1009                         Q_EMIT openDccChat(QString());
1010                         break;
1011                     case 2:
1012                         Q_EMIT openDccChat(parameterList[1]);
1013                         break;
1014                     default:
1015                         result = usage(i18n("Usage: %1DCC [CHAT [nickname]]",
1016                                             Preferences::self()->commandChar()));
1017                 }
1018             }
1019             else if (dccType == QLatin1String("whiteboard"))
1020             {
1021                 //dcc whiteboard nick
1022                 switch (parameterList.count())
1023                 {
1024                     case 1:
1025                         Q_EMIT openDccWBoard(QString());
1026                         break;
1027                     case 2:
1028                         Q_EMIT openDccWBoard(parameterList[1]);
1029                         break;
1030                     default:
1031                         result = usage(i18n("Usage: %1DCC [WHITEBOARD [nickname]]",
1032                                             Preferences::self()->commandChar()));
1033                 }
1034             }
1035             else
1036                 result = error(i18n("Unrecognized command %1DCC %2. Possible commands are SEND, "
1037                                     "CHAT, CLOSE, GET, WHITEBOARD.",
1038                                     Preferences::self()->commandChar(), parameterList[0]));
1039         }
1040 
1041         return result;
1042     }
1043 
sendRequest(const QString & recipient,const QString & fileName,const QString & address,quint16 port,quint64 size)1044     OutputFilterResult OutputFilter::sendRequest(const QString &recipient, const QString &fileName,
1045                                                  const QString &address, quint16 port, quint64 size)
1046     {
1047         OutputFilterResult result;
1048         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ")
1049             + fileName
1050             + QLatin1Char(' ') + address + QLatin1Char(' ') + QString::number(port) + QLatin1Char(' ')
1051             + QString::number(size) + QLatin1Char('\x01');
1052 
1053         return result;
1054     }
1055 
passiveSendRequest(const QString & recipient,const QString & fileName,const QString & address,quint64 size,const QString & token)1056     OutputFilterResult OutputFilter::passiveSendRequest(const QString& recipient,
1057                                                         const QString &fileName,
1058                                                         const QString &address,
1059                                                         quint64 size, const QString &token)
1060     {
1061         OutputFilterResult result;
1062         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ")
1063             + fileName
1064             + QLatin1Char(' ') + address + QLatin1String(" 0 ") + QString::number(size) + QLatin1Char(' ')
1065             + token + QLatin1Char('\x01');
1066 
1067         return result;
1068     }
1069 
1070     // Accepting Resume Request
acceptResumeRequest(const QString & recipient,const QString & fileName,quint16 port,quint64 startAt)1071     OutputFilterResult OutputFilter::acceptResumeRequest(const QString &recipient,
1072                                                          const QString &fileName,
1073                                                          quint16 port, quint64 startAt)
1074     {
1075         OutputFilterResult result;
1076         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC ACCEPT ") + fileName + QLatin1Char(' ') +
1077                           QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char('\x01');
1078 
1079         return result;
1080     }
1081 
1082     // Accepting Passive Resume Request
acceptPassiveResumeRequest(const QString & recipient,const QString & fileName,quint16 port,quint64 startAt,const QString & token)1083     OutputFilterResult OutputFilter::acceptPassiveResumeRequest(const QString &recipient,
1084                                                                 const QString &fileName,
1085                                                                 quint16 port, quint64 startAt,
1086                                                                 const QString &token)
1087     {
1088         OutputFilterResult result;
1089         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC ACCEPT ") + fileName + QLatin1Char(' ') +
1090                           QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char(' ') +
1091                           token + QLatin1Char('\x01');
1092 
1093         return result;
1094     }
1095 
1096     // Requesting Resume Request
resumeRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt)1097     OutputFilterResult OutputFilter::resumeRequest(const QString &sender, const QString &fileName,
1098                                                    quint16 port, KIO::filesize_t startAt)
1099     {
1100         OutputFilterResult result;
1101         result.toServer = QLatin1String("PRIVMSG ") + sender + QLatin1String(" :\x01""DCC RESUME ") + fileName + QLatin1Char(' ') +
1102                           QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char('\x01');
1103 
1104         return result;
1105     }
1106 
1107     // Requesting Passive Resume Request
resumePassiveRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt,const QString & token)1108     OutputFilterResult OutputFilter::resumePassiveRequest(const QString &sender,
1109                                                           const QString &fileName,
1110                                                           quint16 port, KIO::filesize_t startAt,
1111                                                           const QString &token)
1112     {
1113         OutputFilterResult result;
1114         result.toServer = QLatin1String("PRIVMSG ") + sender + QLatin1String(" :\x01""DCC RESUME ") + fileName + QLatin1Char(' ')
1115                           + QString::number(port) + QLatin1Char(' ') + QString::number(startAt) + QLatin1Char(' ')
1116                           + token + QLatin1Char('\x01');
1117 
1118         return result;
1119     }
1120 
1121     // Accept Passive Send Request, there active doesn't need that
acceptPassiveSendRequest(const QString & recipient,const QString & fileName,const QString & address,quint16 port,quint64 size,const QString & token)1122     OutputFilterResult OutputFilter::acceptPassiveSendRequest(const QString& recipient,
1123                                                               const QString &fileName,
1124                                                               const QString &address,
1125                                                               quint16 port, quint64 size,
1126                                                               const QString &token)
1127     {
1128         OutputFilterResult result;
1129         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC SEND ")
1130             + fileName
1131             + QLatin1Char(' ') + address + QLatin1Char(' ') + QString::number(port)
1132             + QLatin1Char(' ') + QString::number(size) + QLatin1Char(' ') + token + QLatin1Char('\x01');
1133 
1134         return result;
1135     }
1136 
1137     // Rejecting quened dcc receive, is not send when abort an active send
rejectDccSend(const QString & partnerNick,const QString & fileName)1138     OutputFilterResult OutputFilter::rejectDccSend(const QString& partnerNick, const QString & fileName)
1139     {
1140         OutputFilterResult result;
1141         result.toServer = QLatin1String("NOTICE ") + partnerNick + QLatin1String(" :\x01""DCC REJECT SEND ")
1142                           + fileName + QLatin1Char('\x01');
1143 
1144         return result;
1145     }
1146 
rejectDccChat(const QString & partnerNick,const QString & extension)1147     OutputFilterResult OutputFilter::rejectDccChat(const QString & partnerNick, const QString& extension)
1148     {
1149         OutputFilterResult result;
1150         result.toServer = QLatin1String("NOTICE ") + partnerNick + QLatin1String(" :\x01""DCC REJECT CHAT ") + extension.toUpper() + QLatin1Char('\x01');
1151 
1152         return result;
1153     }
1154 
requestDccChat(const QString & partnerNick,const QString & extension,const QString & numericalOwnIp,quint16 ownPort)1155     OutputFilterResult OutputFilter::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort)
1156     {
1157         OutputFilterResult result;
1158         result.toServer = QLatin1String("PRIVMSG ") + partnerNick + QLatin1String(" :\x01""DCC CHAT ") +
1159                           extension.toUpper() + QLatin1Char(' ') + numericalOwnIp + QLatin1Char(' ') +
1160                           QString::number(ownPort) + QLatin1Char('\x01');
1161         return result;
1162     }
1163 
passiveChatRequest(const QString & recipient,const QString & extension,const QString & address,const QString & token)1164     OutputFilterResult OutputFilter::passiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token)
1165     {
1166         OutputFilterResult result;
1167         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC CHAT ") +
1168                           extension.toUpper() + QLatin1Char(' ') + address + QLatin1String(" 0 ") +
1169                           token + QLatin1Char('\x01');
1170         return result;
1171     }
1172 
acceptPassiveChatRequest(const QString & recipient,const QString & extension,const QString & numericalOwnIp,quint16 ownPort,const QString & token)1173     OutputFilterResult OutputFilter::acceptPassiveChatRequest(const QString& recipient, const QString& extension, const QString& numericalOwnIp, quint16 ownPort, const QString& token)
1174     {
1175         OutputFilterResult result;
1176         result.toServer = QLatin1String("PRIVMSG ") + recipient + QLatin1String(" :\x01""DCC CHAT ") +
1177                           extension.toUpper() + QLatin1Char(' ') + numericalOwnIp + QLatin1Char(' ') + QString::number(ownPort) + QLatin1Char(' ') + token + QLatin1Char('\x01');
1178         return result;
1179     }
1180 
command_invite(const OutputFilterInput & input)1181     OutputFilterResult OutputFilter::command_invite(const OutputFilterInput& input)
1182     {
1183         OutputFilterResult result;
1184 
1185         if (input.parameter.isEmpty())
1186             result = usage(i18n("Usage: %1INVITE <nick> [channel]", Preferences::self()->commandChar()));
1187         else
1188         {
1189             const QString nick = input.parameter.section(QLatin1Char(' '), 0, 0, QString::SectionSkipEmpty);
1190             QString channel = input.parameter.section(QLatin1Char(' '), 1, 1, QString::SectionSkipEmpty);
1191 
1192             if (channel.isEmpty())
1193             {
1194                 if (isAChannel(input.destination))
1195                     channel = input.destination;
1196                 else
1197                     result = error(i18n("%1INVITE without channel name works only from within channels.",
1198                                         Preferences::self()->commandChar()));
1199             }
1200 
1201             if (!channel.isEmpty())
1202             {
1203                 if (isAChannel(channel))
1204                     result.toServer = QLatin1String("INVITE ") + nick + QLatin1Char(' ') + channel;
1205                 else
1206                     result = error(i18n("%1 is not a channel.", channel));
1207             }
1208         }
1209 
1210         return result;
1211     }
1212 
command_exec(const OutputFilterInput & input)1213     OutputFilterResult OutputFilter::command_exec(const OutputFilterInput& input)
1214     {
1215         OutputFilterResult result;
1216 
1217         if (input.parameter.isEmpty())
1218             result = usage(i18n("Usage: %1EXEC [-SHOWPATH] <script> [parameter list]",
1219                 Preferences::self()->commandChar()));
1220         else
1221         {
1222             QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1223 
1224             if (parameterList.length() >= 2 && isParameter(QStringLiteral("showpath"), parameterList[0]) && !parameterList[1].isEmpty())
1225             {
1226                 result = info(i18nc("%2 is a filesystem path to the script file",
1227                     "The script file '%1' was found at: %2",
1228                     parameterList[1],
1229                     ScriptLauncher::scriptPath(parameterList[1])));
1230             }
1231             else if (!parameterList[0].contains(QLatin1String("../")))
1232                 Q_EMIT launchScript(m_server->connectionId(), input.destination, input.parameter);
1233             else
1234                 result = error(i18n("The script name may not contain \"../\"."));
1235         }
1236 
1237         return result;
1238     }
1239 
command_raw(const OutputFilterInput & input)1240     OutputFilterResult OutputFilter::command_raw(const OutputFilterInput& input)
1241     {
1242         OutputFilterResult result;
1243 
1244         if (input.parameter.isEmpty() || input.parameter == QLatin1String("open"))
1245             Q_EMIT openRawLog(true);
1246         else if (input.parameter == QLatin1String("close"))
1247             Q_EMIT closeRawLog();
1248         else
1249             result = usage(i18n("Usage: %1RAW [OPEN | CLOSE]", Preferences::self()->commandChar()));
1250 
1251         return result;
1252     }
1253 
command_notify(const OutputFilterInput & input)1254     OutputFilterResult OutputFilter::command_notify(const OutputFilterInput& input)
1255     {
1256         OutputFilterResult result;
1257 
1258         int serverGroupId = -1;
1259 
1260         if (m_server->getServerGroup())
1261             serverGroupId = m_server->getServerGroup()->id();
1262 
1263         QStringList added;
1264         QStringList removed;
1265 
1266         if (!input.parameter.isEmpty() && serverGroupId != -1)
1267         {
1268 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1269             const QStringList list = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts);
1270 #else
1271             const QStringList list = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1272 #endif
1273 
1274             for (const QString& parameter : list) {
1275                 // Try to remove current pattern.
1276                 if (!Preferences::removeNotify(serverGroupId, parameter))
1277                 {
1278                     // If remove failed, try to add it instead.
1279                     if (Preferences::addNotify(serverGroupId, parameter))
1280                         added << parameter;
1281                 }
1282                 else
1283                     removed << parameter;
1284             }
1285         }
1286 
1287         QString list = Preferences::notifyListByGroupId(serverGroupId).join(QLatin1String(", "));
1288 
1289         if (list.isEmpty())
1290         {
1291             if (!removed.isEmpty())
1292                 result.output = i18nc("%1 = Comma-separated list of nicknames",
1293                                       "The notify list has been emptied (removed %1).",
1294                                       removed.join(QLatin1String(", ")));
1295             else
1296                 result.output = i18n("The notify list is currently empty.");
1297         }
1298         else
1299         {
1300             if (!added.isEmpty() && removed.isEmpty())
1301                 result.output = i18nc("%1 and %2 = Comma-separated lists of nicknames",
1302                                       "Current notify list: %1 (added %2).",
1303                                       list, added.join(QLatin1String(", ")));
1304             else if (!removed.isEmpty() && added.isEmpty())
1305                 result.output = i18nc("%1 and %2 = Comma-separated lists of nicknames",
1306                                       "Current notify list: %1 (removed %2).",
1307                                       list, removed.join(QLatin1String(", ")));
1308             else if (!added.isEmpty() && !removed.isEmpty())
1309                 result.output = i18nc("%1, %2 and %3 = Comma-separated lists of nicknames",
1310                                       "Current notify list: %1 (added %2, removed %3).",
1311                                       list, added.join(QLatin1String(", ")), removed.join(QLatin1String(", ")));
1312             else
1313                 result.output = i18nc("%1 = Comma-separated list of nicknames",
1314                                       "Current notify list: %1.",
1315                                       list);
1316         }
1317 
1318         result.type = Program;
1319         result.typeString = i18nc("Message type", "Notify");
1320 
1321         return result;
1322     }
1323 
command_oper(const OutputFilterInput & input)1324     OutputFilterResult OutputFilter::command_oper(const OutputFilterInput& input)
1325     {
1326         OutputFilterResult result;
1327 
1328         QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1329 
1330         if (input.parameter.isEmpty() || parameterList.count() == 1)
1331         {
1332             QString nick((parameterList.count() == 1) ? parameterList[0] : input.myNick);
1333 
1334             QPointer<KPasswordDialog> dialog = new KPasswordDialog(nullptr, KPasswordDialog::ShowUsernameLine);
1335             dialog->setPrompt(i18n("Enter username and password for IRC operator privileges:"));
1336             dialog->setUsername(nick);
1337             dialog->setWindowTitle(i18n("IRC Operator Password"));
1338             if (dialog->exec()) {
1339                 result.toServer = QLatin1String("OPER ") + dialog->username() + QLatin1Char(' ') + dialog->password();
1340             }
1341 
1342             delete dialog;
1343         }
1344         else
1345             result.toServer = QLatin1String("OPER ") + input.parameter;
1346 
1347         return result;
1348     }
1349 
command_ban(const OutputFilterInput & input)1350     OutputFilterResult OutputFilter::command_ban(const OutputFilterInput& input)
1351     {
1352         return handleBan(input, false);
1353     }
1354 
command_kickban(const OutputFilterInput & input)1355     OutputFilterResult OutputFilter::command_kickban(const OutputFilterInput& input)
1356     {
1357         return handleBan(input, true);
1358     }
1359 
handleBan(const OutputFilterInput & input,bool kick)1360     OutputFilterResult OutputFilter::handleBan(const OutputFilterInput& input, bool kick)
1361     {
1362         OutputFilterResult result;
1363 
1364         // assume incorrect syntax first
1365         bool showUsage = true;
1366 
1367         if (!input.parameter.isEmpty())
1368         {
1369             QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1370             QString channel;
1371             QString option;
1372             // check for option
1373             bool host = isParameter(QStringLiteral("host"), parameterList[0]);
1374             bool domain = isParameter(QStringLiteral("domain"), parameterList[0]);
1375             bool uhost = isParameter(QStringLiteral("userhost"), parameterList[0]);
1376             bool udomain = isParameter(QStringLiteral("userdomain"), parameterList[0]);
1377 
1378             // remove possible option
1379             if (host || domain || uhost || udomain)
1380             {
1381                 option = parameterList[0].mid(1);
1382                 parameterList.pop_front();
1383             }
1384 
1385             // look for channel / ban mask
1386             if (!parameterList.isEmpty()) {
1387                 // user specified channel
1388                 if (isAChannel(parameterList[0]))
1389                 {
1390                     channel = parameterList[0];
1391                     parameterList.pop_front();
1392                 }
1393                 // no channel, so assume current destination as channel
1394                 else if (isAChannel(input.destination))
1395                     channel = input.destination;
1396                 else
1397                 {
1398                     // destination is no channel => error
1399                     if (!kick)
1400                         result = error(i18n("%1BAN without channel name works only from inside a channel.",
1401                                             Preferences::self()->commandChar()));
1402                     else
1403                         result = error(i18n("%1KICKBAN without channel name works only from inside a channel.",
1404                                             Preferences::self()->commandChar()));
1405 
1406                     // no usage information after error
1407                     showUsage = false;
1408                 }
1409                 // signal server to ban this user if all went fine
1410                 if (!channel.isEmpty() && !parameterList.isEmpty())
1411                 {
1412                     if (kick)
1413                     {
1414                         QString victim = parameterList[0];
1415                         parameterList.pop_front();
1416 
1417                         QString reason = parameterList.join(QLatin1Char(' '));
1418 
1419                         if (reason.isEmpty())
1420                             reason = m_server->getIdentity()->getKickReason();
1421 
1422                         result.toServer = QLatin1String("KICK ") + channel + QLatin1Char(' ') + victim + QLatin1String(" :") + reason;
1423 
1424                         Q_EMIT banUsers(QStringList(victim), channel, option);
1425                     }
1426                     else
1427                         Q_EMIT banUsers(parameterList, channel, option);
1428 
1429                     // syntax was correct, so reset flag
1430                     showUsage = false;
1431                 }
1432             }
1433         }
1434 
1435         if (showUsage)
1436         {
1437             if (!kick)
1438                 result = usage(i18n("Usage: %1BAN [-HOST | -DOMAIN | -USERHOST | -USERDOMAIN] "
1439                                     "[channel] <nickname | mask>", Preferences::self()->commandChar()));
1440             else
1441                 result = usage(i18n("Usage: %1KICKBAN [-HOST | -DOMAIN | -USERHOST | -USERDOMAIN] "
1442                                     "[channel] <nickname | mask> [reason]",
1443                                     Preferences::self()->commandChar()));
1444         }
1445 
1446         return result;
1447     }
1448 
1449     // finally set the ban
execBan(const QString & mask,const QString & channel)1450     OutputFilterResult OutputFilter::execBan(const QString& mask, const QString& channel)
1451     {
1452         OutputFilterResult result;
1453         result.toServer = QLatin1String("MODE ") + channel + QLatin1String(" +b ") + mask;
1454         return result;
1455     }
1456 
command_unban(const OutputFilterInput & input)1457     OutputFilterResult OutputFilter::command_unban(const OutputFilterInput& input)
1458     {
1459         OutputFilterResult result;
1460 
1461         // assume incorrect syntax first
1462         bool showUsage = true;
1463 
1464         if (!input.parameter.isEmpty())
1465         {
1466             QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1467             QString channel;
1468 
1469             // if the user specified a channel
1470             if (isAChannel(parameterList[0]))
1471             {
1472                 // get channel
1473                 channel = parameterList[0];
1474                 // remove channel from parameter list
1475                 parameterList.pop_front();
1476             }
1477             // otherwise the current destination must be a channel
1478             else if (isAChannel(input.destination))
1479                 channel = input.destination;
1480             else
1481             {
1482                 // destination is no channel => error
1483                 result = error(i18n("%1UNBAN without channel name works only from inside a channel.",
1484                                     Preferences::self()->commandChar()));
1485                 // no usage information after error
1486                 showUsage = false;
1487             }
1488             // if all went good, signal server to unban this mask
1489             if (!channel.isEmpty() && !parameterList.isEmpty()) {
1490                 Q_EMIT unbanUsers(parameterList[0], channel);
1491                 // syntax was correct, so reset flag
1492                 showUsage = false;
1493             }
1494         }
1495 
1496         if (showUsage)
1497             result = usage(i18n("Usage: %1UNBAN [channel] pattern", Preferences::self()->commandChar()));
1498 
1499         return result;
1500     }
1501 
execUnban(const QString & mask,const QString & channel)1502     OutputFilterResult OutputFilter::execUnban(const QString& mask, const QString& channel)
1503     {
1504         OutputFilterResult result;
1505         result.toServer = QLatin1String("MODE ") + channel + QLatin1String(" -b ") + mask;
1506         return result;
1507     }
1508 
command_ignore(const OutputFilterInput & input)1509     OutputFilterResult OutputFilter::command_ignore(const OutputFilterInput& input)
1510     {
1511         OutputFilterResult result;
1512 
1513         // assume incorrect syntax first
1514         bool showUsage = true;
1515 
1516         // did the user give parameters at all?
1517         if (!input.parameter.isEmpty())
1518         {
1519             QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1520 
1521             // if nothing else said, only ignore channels and queries
1522             int value = Ignore::Channel | Ignore::Query;
1523 
1524             // user specified -all option
1525             if (isParameter(QStringLiteral("all"), parameterList[0]))
1526             {
1527                 // ignore everything
1528                 value = Ignore::All;
1529                 parameterList.pop_front();
1530             }
1531 
1532             // were there enough parameters?
1533             if (parameterList.count() >= 1)
1534             {
1535                 for (QString parameter : qAsConst(parameterList)) {
1536                     if (!parameter.contains(QLatin1Char('!')))
1537                         parameter += QLatin1String("!*");
1538 
1539                     Preferences::removeIgnore(parameter);
1540                     Preferences::addIgnore(parameter + QLatin1Char(',') + QString::number(value));
1541                 }
1542 
1543                 result.output = i18n("Added %1 to your ignore list.", parameterList.join(QLatin1String(", ")));
1544                 result.typeString = i18n("Ignore");
1545                 result.type = Program;
1546 
1547                 // all went fine, so show no error message
1548                 showUsage = false;
1549             }
1550         }
1551 
1552         if (showUsage)
1553             result = usage(i18n("Usage: %1IGNORE [ -ALL ] <user 1> <user 2> ... <user n>",
1554                                 Preferences::self()->commandChar()));
1555 
1556         return result;
1557     }
1558 
command_unignore(const OutputFilterInput & input)1559     OutputFilterResult OutputFilter::command_unignore(const OutputFilterInput& input)
1560     {
1561         OutputFilterResult result;
1562 
1563         if (input.parameter.isEmpty())
1564             result = usage(i18n("Usage: %1UNIGNORE <user 1> <user 2> ... <user n>",
1565                                 Preferences::self()->commandChar()));
1566         else
1567         {
1568             QString unignore = input.parameter.simplified();
1569             const QStringList unignoreList = unignore.split(QLatin1Char(' '));
1570 
1571             QStringList succeeded;
1572             QStringList failed;
1573 
1574             // Iterate over potential unignores
1575             for (const QString &uign : unignoreList) {
1576                 // If pattern looks incomplete, try to complete it
1577                 if (!uign.contains(QLatin1Char('!'))) {
1578                     QString fixedPattern = uign;
1579                     fixedPattern += QLatin1String("!*");
1580 
1581                     bool success = false;
1582 
1583                     // Try to remove completed pattern
1584                     if (Preferences::removeIgnore(fixedPattern))
1585                     {
1586                         succeeded.append(fixedPattern);
1587                         success = true;
1588                     }
1589 
1590                     // Try to remove the incomplete version too, in case it was added via the GUI ...
1591                     // FIXME: Validate patterns in GUI?
1592                     if (Preferences::removeIgnore(uign))
1593                     {
1594                         succeeded.append(uign);
1595                         success = true;
1596                     }
1597 
1598                     if (!success)
1599                         failed.append(uign + QLatin1String("[!*]"));
1600                 }
1601                 // Try to remove seemingly complete pattern
1602                 else if (Preferences::removeIgnore(uign))
1603                     succeeded.append(uign);
1604                 // Failed to remove given complete pattern
1605                 else
1606                     failed.append(uign);
1607             }
1608 
1609             // Print all successful unignores, in case there were any
1610             if (succeeded.count() >= 1)
1611             {
1612                 result.output = i18n("Removed %1 from your ignore list.", succeeded.join(QLatin1String(", ")));
1613                 result.typeString = i18n("Ignore");
1614                 result.type = Program;
1615             }
1616 
1617             // Any failed unignores
1618             if (failed.count() >= 1)
1619                 result = error(i18np("No such ignore: %2", "No such ignores: %2", failed.count(), failed.join(QLatin1String(", "))));
1620         }
1621 
1622         return result;
1623     }
1624 
command_server(const OutputFilterInput & input)1625     OutputFilterResult OutputFilter::command_server(const OutputFilterInput& input)
1626     {
1627         if (input.parameter.isEmpty() && !m_server->isConnected() && !m_server->isConnecting())
1628             Q_EMIT reconnectServer(QString());
1629         else
1630         {
1631             const QStringList parameterList = input.parameter.split(QLatin1Char(' '));
1632 
1633             if (parameterList.count() == 3)
1634                 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], parameterList[1], parameterList[2]);
1635             else if (parameterList.count() == 2)
1636             {
1637                 if (parameterList[0].contains(QRegularExpression(QStringLiteral(":[0-9]+$"))))
1638                     Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], QString(), parameterList[1]);
1639                 else
1640                     Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0], parameterList[1]);
1641             }
1642             else
1643                 Q_EMIT connectTo(Konversation::CreateNewConnection, parameterList[0]);
1644         }
1645 
1646         return OutputFilterResult();
1647     }
1648 
command_charset(const OutputFilterInput & input)1649     OutputFilterResult OutputFilter::command_charset(const OutputFilterInput& input)
1650     {
1651         if (input.parameter.isEmpty())
1652             return info(i18n("Current encoding is: %1",
1653                              QString::fromUtf8(m_server->getIdentity()->getCodec()->name())));
1654 
1655         OutputFilterResult result;
1656 
1657         QString shortName = Konversation::IRCCharsets::self()->ambiguousNameToShortName(input.parameter);
1658 
1659         if (!shortName.isEmpty())
1660         {
1661             m_server->getIdentity()->setCodecName(shortName);
1662             Q_EMIT encodingChanged();
1663             result = info (i18n("Switched to %1 encoding.", shortName));
1664         }
1665         else
1666             result = error(i18n("%1 is not a valid encoding.", input.parameter));
1667 
1668         return result;
1669     }
1670 
command_encoding(const OutputFilterInput & input)1671     OutputFilterResult OutputFilter::command_encoding(const OutputFilterInput& input)
1672     {
1673         return command_charset(input);
1674     }
1675 
command_setkey(const OutputFilterInput & input)1676     OutputFilterResult OutputFilter::command_setkey(const OutputFilterInput& input)
1677     {
1678         #ifdef HAVE_QCA2
1679 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1680         QStringList parms = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts);
1681 #else
1682         QStringList parms = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1683 #endif
1684 
1685         if (parms.count() == 1 && !input.destination.isEmpty())
1686             parms.prepend(input.destination);
1687         else if (parms.count() < 2)
1688             return usage(i18n("Usage: %1setkey <nick|channel> <key> sets the encryption key "
1689                               "for nick or channel. %1setkey <key> when in a channel or query "
1690                               "tab sets the key for it. The key field recognizes \"cbc:\" and "
1691                               "\"ecb:\" prefixes to set the block cipher mode of operation to "
1692                               "either Cipher-Block Chaining or Electronic Codebook. The mode it "
1693                               "defaults to when no prefix is given can be changed in the "
1694                               "configuration dialog under Behavior -> Connection -> Encryption "
1695                               "-> Default Encryption Type, with the default for that setting being "
1696                               "Electronic Codebook (ECB).",
1697                               Preferences::self()->commandChar()));
1698 
1699         if (!Cipher::isFeatureAvailable(Cipher::Blowfish))
1700             return error(i18n("Unable to set an encryption key for %1.", parms[0]) + QLatin1Char(' ') + Cipher::runtimeError());
1701 
1702         m_server->setKeyForRecipient(parms[0], QStringList(parms.mid(1)).join(QLatin1Char(' ')).toUtf8());
1703 
1704         if (isAChannel(parms[0]) && m_server->getChannelByName(parms[0]))
1705             m_server->getChannelByName(parms[0])->setEncryptedOutput(true);
1706         else if (m_server->getQueryByName(parms[0]))
1707             m_server->getQueryByName(parms[0])->setEncryptedOutput(true);
1708 
1709         return info(i18n("The key for %1 has been set.", parms[0]));
1710         #else
1711         Q_UNUSED(input)
1712         return error(i18n("Setting an encryption key requires Konversation to have been built "
1713                           "with support for the Qt Cryptographic Architecture (QCA) library. "
1714                           "Contact your distributor about a Konversation package with QCA "
1715                           "support, or rebuild Konversation with QCA present."));
1716         #endif
1717     }
1718 
command_keyx(const OutputFilterInput & input)1719     OutputFilterResult OutputFilter::command_keyx(const OutputFilterInput& input)
1720     {
1721         #ifdef HAVE_QCA2
1722 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1723         QStringList parms = input.parameter.split(QLatin1Char(' '), QString::SkipEmptyParts);
1724 #else
1725         QStringList parms = input.parameter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1726 #endif
1727 
1728         if (parms.isEmpty() && !input.destination.isEmpty())
1729             parms.prepend(input.destination);
1730         else if (parms.count() !=1)
1731             return usage(i18n("Usage: %1keyx <nick|channel> triggers DH1080 key exchange with the target.",
1732                               Preferences::self()->commandChar()));
1733         if (!Cipher::isFeatureAvailable(Cipher::DH))
1734             return error(i18n("Unable to request a key exchange from %1.", parms[0]) + QLatin1Char(' ') + Cipher::runtimeError());
1735 
1736         m_server->initKeyExchange(parms[0]);
1737 
1738         return info(i18n("Beginning DH1080 key exchange with %1.", parms[0]));
1739         #else
1740         Q_UNUSED(input)
1741         return error(i18n("Setting an encryption key requires Konversation to have been built "
1742                           "with support for the Qt Cryptographic Architecture (QCA) library. "
1743                           "Contact your distributor about a Konversation package with QCA "
1744                           "support, or rebuild Konversation with QCA present."));
1745         #endif
1746     }
1747 
command_delkey(const OutputFilterInput & _input)1748     OutputFilterResult OutputFilter::command_delkey(const OutputFilterInput& _input)
1749     {
1750         OutputFilterInput input(_input);
1751         OutputFilterResult result;
1752 
1753         if (input.parameter.isEmpty())
1754             input.parameter = input.destination;
1755 
1756         if (input.parameter.isEmpty() || input.parameter.contains(QLatin1Char(' ')))
1757             return usage(i18n("Usage: %1delkey <nick> or <channel> deletes the encryption key for "
1758                               "nick or channel", Preferences::self()->commandChar()));
1759 
1760         if (!m_server->getKeyForRecipient(input.parameter).isEmpty())
1761         {
1762             m_server->setKeyForRecipient(input.parameter, "");
1763 
1764             if (isAChannel(input.parameter) && m_server->getChannelByName(input.parameter))
1765                 m_server->getChannelByName(input.parameter)->setEncryptedOutput(false);
1766             else if (m_server->getQueryByName(input.parameter))
1767                 m_server->getQueryByName(input.parameter)->setEncryptedOutput(false);
1768 
1769             result = info(i18n("The key for %1 has been deleted.", input.parameter));
1770         }
1771         else
1772             result = error(i18n("No key has been set for %1.", input.parameter));
1773 
1774         return result;
1775     }
1776 
command_showkey(const OutputFilterInput & _input)1777     OutputFilterResult OutputFilter::command_showkey(const OutputFilterInput& _input)
1778     {
1779         OutputFilterInput input(_input);
1780 
1781         if (input.parameter.isEmpty())
1782             input.parameter = input.destination;
1783 
1784         const QString key = QString::fromUtf8(m_server->getKeyForRecipient(input.parameter));
1785 
1786         QWidget* mw = Application::instance()->getMainWindow();
1787 
1788         if (!key.isEmpty())
1789             KMessageBox::information(mw, i18n("The key for %1 is \"%2\".", input.parameter, key),
1790                                      i18n("Blowfish"));
1791         else
1792             KMessageBox::information(mw, i18n("No key has been set for %1.", input.parameter));
1793 
1794         return OutputFilterResult();
1795     }
1796 
command_kill(const OutputFilterInput & input)1797     OutputFilterResult OutputFilter::command_kill(const OutputFilterInput& input)
1798     {
1799         OutputFilterResult result;
1800 
1801         if (input.parameter.isEmpty())
1802             result = usage(i18n("Usage: %1KILL <nick> [comment]", Preferences::self()->commandChar()));
1803         else
1804         {
1805             const QString victim = input.parameter.section(QLatin1Char(' '), 0, 0);
1806 
1807             result.toServer = QLatin1String("KILL ") + victim + QLatin1String(" :") + input.parameter.mid(victim.length() + 1);
1808         }
1809 
1810         return result;
1811     }
1812 
command_dns(const OutputFilterInput & input)1813     OutputFilterResult OutputFilter::command_dns(const OutputFilterInput& input)
1814     {
1815         if (input.parameter.isEmpty())
1816             return usage(i18n("Usage: %1DNS <nick>", Preferences::self()->commandChar()));
1817         else
1818             new OutputFilterResolveJob(input);
1819 
1820         return OutputFilterResult();
1821     }
1822 
command_list(const OutputFilterInput & input)1823     OutputFilterResult OutputFilter::command_list(const OutputFilterInput& input)
1824     {
1825         Q_EMIT openChannelList(input.parameter);
1826 
1827         return OutputFilterResult();
1828     }
1829 
command_konsole(const OutputFilterInput &)1830     OutputFilterResult OutputFilter::command_konsole(const OutputFilterInput& /* input */)
1831     {
1832         Q_EMIT openKonsolePanel();
1833 
1834         return OutputFilterResult();
1835     }
1836 
command_queuetuner(const OutputFilterInput & input)1837     OutputFilterResult OutputFilter::command_queuetuner(const OutputFilterInput& input)
1838     {
1839         Application *konvApp = Application::instance();
1840 
1841         if (input.parameter.isEmpty() || input.parameter == QLatin1String("on"))
1842             konvApp->showQueueTuner(true);
1843         else if (input.parameter == QLatin1String("off"))
1844             konvApp->showQueueTuner(false);
1845         else
1846             return usage(i18n("Usage: %1queuetuner [on | off]", Preferences::self()->commandChar()));
1847 
1848         return OutputFilterResult();
1849     }
1850 
command_sayversion(const OutputFilterInput & input)1851     OutputFilterResult OutputFilter::command_sayversion(const OutputFilterInput& input)
1852     {
1853         OutputFilterResult result;
1854         result.output = input.parameter;
1855 
1856         QTextStream serverOut(&result.toServer);
1857         QTextStream myOut(&result.output); //<--because seek is unimplemented in QTextStreamPrivate::write(const QString &data)
1858         myOut
1859             << "Konversation: " << qApp->applicationVersion()
1860             << ", Qt " << QString::fromLatin1(qVersion())
1861             << ", KDE Frameworks " << QStringLiteral(KXMLGUI_VERSION_STRING);
1862             ;
1863         if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION") && !qEnvironmentVariableIsEmpty("KDE_SESSION_VERSION"))
1864             myOut << ", Plasma " << QString::fromLatin1(qgetenv("KDE_SESSION_VERSION"));
1865         else
1866             myOut << ", no Plasma";
1867 
1868         serverOut << "PRIVMSG " << input.destination << " :" << result.output;
1869         return result;
1870     }
1871 
command_cycle(const OutputFilterInput & input)1872     OutputFilterResult OutputFilter::command_cycle(const OutputFilterInput& input)
1873     {
1874         if (input.parameter.isEmpty())
1875         {
1876             if (input.context)
1877                 input.context->cycle();
1878             else
1879                 qCDebug(KONVERSATION_LOG) << "Parameter-less /cycle without an input context can't work.";
1880         }
1881         else
1882         {
1883             if (isParameter(QStringLiteral("app"), input.parameter))
1884             {
1885                 Application *konvApp = Application::instance();
1886 
1887                 konvApp->restart();
1888             }
1889             else if (isParameter(QStringLiteral("server"), input.parameter))
1890             {
1891                 if (m_server)
1892                     m_server->cycle();
1893                 else
1894                     qCDebug(KONVERSATION_LOG) << "Told to cycle the server, but current context doesn't have one.";
1895             }
1896             else if (m_server)
1897             {
1898                 if (isAChannel(input.parameter))
1899                 {
1900                     Channel* channel = m_server->getChannelByName(input.parameter);
1901 
1902                     if (channel) channel->cycle();
1903                 }
1904                 else if (m_server->getQueryByName(input.parameter))
1905                     m_server->getQueryByName(input.parameter)->cycle();
1906                 else
1907                     return usage(i18n("%1CYCLE [-APP | -SERVER] [channel | nickname]", Preferences::self()->commandChar()));
1908             }
1909         }
1910 
1911         return OutputFilterResult();
1912     }
1913 
command_clear(const OutputFilterInput & input)1914     OutputFilterResult OutputFilter::command_clear(const OutputFilterInput& input)
1915     {
1916         if (input.parameter.isEmpty())
1917         {
1918             if (input.context)
1919                 input.context->clear();
1920             else
1921                 qCDebug(KONVERSATION_LOG) << "Parameter-less /clear without an input context can't work.";
1922         }
1923         else if (m_server)
1924         {
1925             if (isParameter(QStringLiteral("all"), input.parameter))
1926                 m_server->getViewContainer()->clearAllViews();
1927             else
1928             {
1929                 if (isAChannel(input.parameter))
1930                 {
1931                     Channel* channel = m_server->getChannelByName(input.parameter);
1932 
1933                     if (channel) channel->clear();
1934                 }
1935                 else if (m_server->getQueryByName(input.parameter))
1936                     m_server->getQueryByName(input.parameter)->clear();
1937                 else
1938                     return usage(i18n("%1CLEAR [-ALL] [channel | nickname]", Preferences::self()->commandChar()));
1939             }
1940         }
1941 
1942         return OutputFilterResult();
1943     }
1944 
command_umode(const OutputFilterInput & input)1945     OutputFilterResult OutputFilter::command_umode(const OutputFilterInput& input)
1946     {
1947         OutputFilterResult result;
1948 
1949         result.toServer = QLatin1String("MODE ") + input.myNick + QLatin1Char(' ') + input.parameter;
1950 
1951         return result;
1952     }
1953 
changeMode(const QString & parameter,const QString & destination,char mode,char giveTake)1954     OutputFilterResult OutputFilter::changeMode(const QString &parameter, const QString& destination,
1955                                                 char mode, char giveTake)
1956     {
1957         OutputFilterResult result;
1958         // TODO: Make sure this works with +l <limit> and +k <password> also!
1959         QString token;
1960         QString tmpToken;
1961         QStringList nickList = parameter.split(QLatin1Char(' '));
1962 
1963         if (!nickList.isEmpty()) {
1964             // Check if the user specified a channel
1965             if(isAChannel(nickList[0]))
1966             {
1967                 token = QLatin1String("MODE ") + nickList[0];
1968                 // remove the first element
1969                 nickList.removeFirst();
1970             }
1971             // Add default destination if it is a channel
1972             else if(isAChannel(destination))
1973             {
1974                 token = QLatin1String("MODE ") + destination;
1975             }
1976 
1977             // Only continue if there was no error
1978             if (!token.isEmpty()) {
1979                 QString modeToken;
1980                 QString nickToken;
1981 
1982                 modeToken = QLatin1Char(' ') + QLatin1Char(giveTake);
1983 
1984                 tmpToken = token;
1985 
1986                 for(int index = 0; index < nickList.count(); index++)
1987                 {
1988                     if(index != 0 && (index % 3) == 0)
1989                     {
1990                         token += modeToken + nickToken;
1991                         result.toServerList.append(token);
1992                         token = tmpToken;
1993                         nickToken.clear();
1994                         modeToken = QLatin1Char(' ') + QLatin1Char(giveTake);
1995                     }
1996                     nickToken += QLatin1Char(' ') + nickList[index];
1997                     modeToken += QLatin1Char(mode);
1998                 }
1999 
2000                 if(!nickToken.isEmpty())
2001                 {
2002                     token += modeToken + nickToken;
2003                     result.toServerList.append(token);
2004                 }
2005             }
2006         }
2007 
2008         return result;
2009     }
2010 
usage(const QString & string)2011     OutputFilterResult OutputFilter::usage(const QString& string)
2012     {
2013         OutputFilterResult result;
2014         result.typeString = i18n("Usage");
2015         result.output = string;
2016         result.type = Program;
2017         return result;
2018     }
2019 
info(const QString & string)2020     OutputFilterResult OutputFilter::info(const QString& string)
2021     {
2022         OutputFilterResult result;
2023         result.typeString = i18n("Info");
2024         result.output = string;
2025         result.type = Program;
2026         return result;
2027     }
2028 
error(const QString & string)2029     OutputFilterResult OutputFilter::error(const QString& string)
2030     {
2031         OutputFilterResult result;
2032         result.typeString = i18n("Error");
2033         result.output = string;
2034         result.type = Program;
2035         return result;
2036     }
2037 
addNickToEmptyNickList(const QString & nick,const QString & parameter)2038     QString OutputFilter::addNickToEmptyNickList(const QString& nick, const QString& parameter)
2039     {
2040         const QStringList nickList = parameter.split(QLatin1Char(' '));
2041         QString newNickList;
2042 
2043         if (nickList.isEmpty())
2044         {
2045             newNickList = nick;
2046         }
2047         // check if list contains only target channel
2048         else if (nickList.count() == 1 && isAChannel(nickList[0]))
2049         {
2050             newNickList = nickList[0] + QLatin1Char(' ') + nick;
2051         }
2052         // list contains at least one nick
2053         else
2054         {
2055             newNickList = parameter;
2056         }
2057 
2058         return newNickList;
2059     }
2060 
2061     // # & + and ! are *often*, but not necessarily, channel identifiers. + and ! are non-RFC,
2062     // so if a server doesn't offer 005 and supports + and ! channels, I think thats broken
2063     // behaviour on their part - not ours.
isAChannel(const QString & check) const2064     bool OutputFilter::isAChannel(const QString &check) const
2065     {
2066         if (check.isEmpty())
2067             return false;
2068         Q_ASSERT(m_server);
2069                                                   // XXX if we ever see the assert, we need the ternary
2070         return m_server? m_server->isAChannel(check) : bool(QStringLiteral("#&").contains(check.at(0)));
2071     }
2072 
isParameter(const QString & parameter,const QString & string) const2073     bool OutputFilter::isParameter(const QString& parameter, const QString& string) const
2074     {
2075         const QString pattern = QRegularExpression::anchoredPattern(QStringLiteral("^[\\-]{1,2}%1$").arg(parameter));
2076         const QRegularExpression rx(pattern, QRegularExpression::CaseInsensitiveOption);
2077 
2078         return rx.match(string).hasMatch();
2079     }
2080 }
2081 
2082 
2083 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on;
2084 // vim: set et sw=4 ts=4 cino=l1,cs,U1:
2085