1 // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*-
2 
3 /*
4     SPDX-License-Identifier: GPL-2.0-or-later
5 
6     SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com>
7     SPDX-FileCopyrightText: 2005 Ismail Donmez <ismail@kde.org>
8     SPDX-FileCopyrightText: 2005-2016 Peter Simonsson <peter.simonsson@gmail.com>
9     SPDX-FileCopyrightText: 2006-2008 Eli J. MacKenzie <argonel at gmail.com>
10     SPDX-FileCopyrightText: 2005-2008 Eike Hein <hein@kde.org>
11 */
12 
13 #include "server.h"
14 
15 #include "ircqueue.h"
16 #include "query.h"
17 #include "channel.h"
18 #include "application.h"
19 #include "connectionmanager.h"
20 #include "dcccommon.h"
21 #include "dccfiledialog.h"
22 #include "transfermanager.h"
23 #include "transfersend.h"
24 #include "transferrecv.h"
25 #include "chat.h"
26 #include "recipientdialog.h"
27 #include "nick.h"
28 #include "irccharsets.h"
29 #include "viewcontainer.h"
30 #include "rawlog.h"
31 #include "channellistpanel.h"
32 #include "scriptlauncher.h"
33 #include "serverison.h"
34 #include "notificationhandler.h"
35 #include "awaymanager.h"
36 #include "ircinput.h"
37 #include "konversation_log.h"
38 
39 #include <KLocalizedString>
40 #include <KShell>
41 #include <KIO/SslUi>
42 
43 #include <QTextCodec>
44 #include <QSslSocket>
45 #include <QStringListModel>
46 #include <QInputDialog>
47 #include <QNetworkProxy>
48 
49 using namespace Konversation;
50 
51 int Server::m_availableConnectionId = 0;
52 
Server(QObject * parent,ConnectionSettings & settings)53 Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent)
54 {
55     m_connectionId = m_availableConnectionId;
56     m_availableConnectionId++;
57 
58     setConnectionSettings(settings);
59 
60     m_connectionState = Konversation::SSNeverConnected;
61 
62     m_recreationScheduled = false;
63 
64     m_delayedConnectTimer = new QTimer(this);
65     m_delayedConnectTimer->setSingleShot(true);
66     connect(m_delayedConnectTimer, &QTimer::timeout, this, &Server::connectToIRCServer);
67 
68     m_reconnectImmediately = false;
69 
70     for (int i=0; i <= Application::instance()->countOfQueues(); i++)
71     {
72         //QList<int> r=Preferences::queueRate(i);
73         auto *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc
74         m_queues.append(q);
75     }
76 
77     m_processingIncoming = false;
78     m_identifyMsg = false;
79     m_capRequested = 0;
80     m_capAnswered = 0;
81     m_capEndDelayed = false;
82     m_autoJoin = false;
83 
84     m_capabilities = NoCapabilies;
85     m_whoRequestsDisabled = false;
86     initCapablityNames();
87 
88     m_nickIndices.clear();
89     m_nickIndices.append(0);
90 
91     m_nickListModel = new QStringListModel(this);
92 
93     m_currentLag = -1;
94     m_rawLog = nullptr;
95     m_channelListPanel = nullptr;
96     m_serverISON = nullptr;
97     m_away = false;
98     m_socket = nullptr;
99     m_prevISONList = QStringList();
100     m_bytesReceived = 0;
101     m_encodedBytesSent=0;
102     m_bytesSent=0;
103     m_linesSent=0;
104     // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused.
105     m_serverNickPrefixModes = QStringLiteral("ovh");
106     m_serverNickPrefixes = QStringLiteral("@+%");
107     m_banAddressListModes = QLatin1Char('b'); // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one
108     m_channelPrefixes = QStringLiteral("#&");
109     m_modesCount = 3;
110     m_sslErrorLock = false;
111     m_topicLength = -1;
112 
113     setObjectName(QLatin1String("server_") + m_connectionSettings.name());
114 
115     setNickname(m_connectionSettings.initialNick());
116     obtainNickInfo(getNickname());
117 
118     m_statusView = getViewContainer()->addStatusView(this);
119 
120     if (Preferences::self()->rawLog())
121         addRawLog(false);
122 
123     m_inputFilter.setServer(this);
124     m_outputFilter = new Konversation::OutputFilter(this);
125 
126     // For /msg query completion
127     m_completeQueryPosition = 0;
128 
129     updateAutoJoin(m_connectionSettings.oneShotChannelList());
130 
131     if (!getIdentity()->getShellCommand().isEmpty())
132         QTimer::singleShot(0, this, &Server::doPreShellCommand);
133     else
134         QTimer::singleShot(0, this, &Server::connectToIRCServer);
135 
136     initTimers();
137 
138     if (getIdentity()->getShellCommand().isEmpty())
139         connectSignals();
140     // TODO FIXME this disappeared in a merge, ensure it should have
141     updateConnectionState(Konversation::SSNeverConnected);
142 
143     m_nickInfoChangedTimer = new QTimer(this);
144     m_nickInfoChangedTimer->setSingleShot(true);
145     m_nickInfoChangedTimer->setInterval(3000);
146     connect(m_nickInfoChangedTimer, &QTimer::timeout, this, &Server::sendNickInfoChangedSignals);
147 
148     m_channelNickChangedTimer = new QTimer(this);
149     m_channelNickChangedTimer->setSingleShot(true);
150     m_channelNickChangedTimer->setInterval(1000);
151     connect(m_channelNickChangedTimer, &QTimer::timeout, this, &Server::sendChannelNickChangedSignals);
152 }
153 
~Server()154 Server::~Server()
155 {
156     //send queued messages
157     qCDebug(KONVERSATION_LOG) << "Server::~Server(" << getServerName() << ")";
158 
159     // Delete helper object.
160     delete m_serverISON;
161     m_serverISON = nullptr;
162 
163     // clear nicks online
164     Q_EMIT nicksNowOnline(this,QStringList(),true);
165 
166     // Make sure no signals get sent to a soon to be dying Server Window
167     if (m_socket)
168     {
169         m_socket->blockSignals(true);
170         m_socket->deleteLater();
171     }
172 
173     delete m_statusView;
174 
175     closeRawLog();
176     closeChannelListPanel();
177 
178     if (m_recreationScheduled)
179     {
180         Konversation::ChannelList channelList;
181 
182         channelList.reserve(m_channelList.size());
183         for (Channel* channel : qAsConst(m_channelList)) {
184             channelList << channel->channelSettings();
185         }
186 
187         m_connectionSettings.setOneShotChannelList(channelList);
188     }
189 
190     qDeleteAll(m_channelList);
191     m_channelList.clear();
192     m_loweredChannelNameHash.clear();
193 
194     qDeleteAll(m_queryList);
195     m_queryList.clear();
196 
197     purgeData();
198 
199     //Delete the queues
200     qDeleteAll(m_queues);
201 
202     Q_EMIT destroyed(m_connectionId);
203 
204     if (m_recreationScheduled)
205     {
206         qRegisterMetaType<ConnectionSettings>("ConnectionSettings");
207         qRegisterMetaType<Konversation::ConnectionFlag>("Konversation::ConnectionFlag");
208 
209         Application* konvApp = Application::instance();
210 
211         QMetaObject::invokeMethod(konvApp->getConnectionManager(), "connectTo", Qt::QueuedConnection,
212             Q_ARG(Konversation::ConnectionFlag, Konversation::CreateNewConnection),
213             Q_ARG(ConnectionSettings, m_connectionSettings));
214     }
215 
216     qCDebug(KONVERSATION_LOG) << "~Server done";
217 }
218 
purgeData()219 void Server::purgeData()
220 {
221     // Delete all the NickInfos and ChannelNick structures.
222     m_allNicks.clear();
223 
224     qDeleteAll(m_joinedChannels);
225     m_joinedChannels.clear();
226 
227     qDeleteAll(m_unjoinedChannels);
228     m_unjoinedChannels.clear();
229 
230     m_queryNicks.clear();
231     delete m_serverISON;
232     m_serverISON = nullptr;
233 
234 }
235 
236 //... so called to match the ChatWindow derivatives.
closeYourself(bool askForConfirmation)237 bool Server::closeYourself(bool askForConfirmation)
238 {
239     m_statusView->closeYourself(askForConfirmation);
240 
241     return true;
242 }
243 
cycle()244 void Server::cycle()
245 {
246     m_recreationScheduled = true;
247 
248     m_statusView->closeYourself();
249 }
250 
doPreShellCommand()251 void Server::doPreShellCommand()
252 {
253     KShell::Errors e;
254     QStringList command = KShell::splitArgs(getIdentity()->getShellCommand(), KShell::TildeExpand, &e);
255     if (e != KShell::NoError)
256     {
257         //FIXME The flow needs to be refactored, add a finally-like method that does the ready-to-connect stuff
258         // "The pre-connect shell command could not be understood!");
259         preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus());
260     }
261     else
262     {
263         // FIXME add i18n, and in preShellCommandExited and preShellCommandError
264         getStatusView()->appendServerMessage(i18n("Info"), i18nc("The command mentioned is executed in a shell prior to connecting.", "Running pre-connect shell command..."));
265 
266         connect(&m_preShellCommand, QOverload<int,QProcess::ExitStatus>::of(&KProcess::finished),
267                 this, &Server::preShellCommandExited);
268         connect(&m_preShellCommand, &KProcess::errorOccurred, this, &Server::preShellCommandError);
269 
270         m_preShellCommand.setProgram(command);
271         m_preShellCommand.start();
272         // NOTE: isConnecting is tested in connectToIRCServer so there's no guard here
273         if (m_preShellCommand.state() == QProcess::NotRunning)
274             preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus());
275     }
276 }
277 
initTimers()278 void Server::initTimers()
279 {
280     m_notifyTimer.setObjectName(QStringLiteral("notify_timer"));
281     m_notifyTimer.setSingleShot(true);
282     m_incomingTimer.setObjectName(QStringLiteral("incoming_timer"));
283     m_pingSendTimer.setSingleShot(true);
284 }
285 
connectSignals()286 void Server::connectSignals()
287 {
288     // Timers
289     connect(&m_incomingTimer, &QTimer::timeout, this, &Server::processIncomingData);
290     connect(&m_notifyTimer, &QTimer::timeout, this, &Server::notifyTimeout);
291     connect(&m_pingResponseTimer, &QTimer::timeout, this, &Server::updateLongPongLag);
292     connect(&m_pingSendTimer, &QTimer::timeout, this, &Server::sendPing);
293 
294     // OutputFilter
295     connect(getOutputFilter(), QOverload<>::of(&OutputFilter::requestDccSend),
296             this, QOverload<>::of(&Server::requestDccSend), Qt::QueuedConnection);
297     connect(getOutputFilter(),QOverload<const QString&>::of(&OutputFilter::requestDccSend),
298             this, QOverload<const QString&>::of(&Server::requestDccSend), Qt::QueuedConnection);
299     connect(getOutputFilter(), &OutputFilter::multiServerCommand,
300         this, &Server::sendMultiServerCommand);
301     connect(getOutputFilter(), &OutputFilter::reconnectServer, this, &Server::reconnectServer);
302     connect(getOutputFilter(), &OutputFilter::disconnectServer, this, &Server::disconnectServer);
303     connect(getOutputFilter(), &OutputFilter::quitServer, this, &Server::quitServer);
304     connect(getOutputFilter(), &OutputFilter::openDccSend,
305             this, [this](const QString& recipient, const QUrl& url) { addDccSend(recipient, url); }, Qt::QueuedConnection);
306     connect(getOutputFilter(), &OutputFilter::openDccChat, this, &Server::openDccChat, Qt::QueuedConnection);
307     connect(getOutputFilter(), &OutputFilter::openDccWBoard, this, &Server::openDccWBoard, Qt::QueuedConnection);
308     connect(getOutputFilter(), &OutputFilter::acceptDccGet,
309         this, &Server::acceptDccGet);
310     connect(getOutputFilter(), &OutputFilter::sendToAllChannels, this, &Server::sendToAllChannels);
311     connect(getOutputFilter(), &OutputFilter::banUsers,
312         this, &Server::requestBan);
313     connect(getOutputFilter(), &OutputFilter::unbanUsers,
314         this, &Server::requestUnban);
315     connect(getOutputFilter(), &OutputFilter::openRawLog, this, &Server::addRawLog);
316     connect(getOutputFilter(), &OutputFilter::closeRawLog, this, &Server::closeRawLog);
317     connect(getOutputFilter(), &OutputFilter::encodingChanged, this, &Server::updateEncoding);
318 
319     Application* konvApp = Application::instance();
320     connect(getOutputFilter(), QOverload<Konversation::ConnectionFlag, const QString& , const QString&, const QString&, const QString&, const QString&, bool>::of(&OutputFilter::connectTo),
321          konvApp->getConnectionManager(), QOverload<Konversation::ConnectionFlag, const QString& , const QString&, const QString&, const QString&, const QString&, bool>::of(&ConnectionManager::connectTo));
322     connect(konvApp->getDccTransferManager(), &DCC::TransferManager::newDccTransferQueued,
323             this, &Server::slotNewDccTransferItemQueued);
324 
325    // ViewContainer
326     connect(this, &Server::showView, getViewContainer(), &ViewContainer::showView);
327     connect(this, &Server::addDccPanel, getViewContainer(), &ViewContainer::addDccPanel);
328     connect(this, QOverload<Konversation::DCC::Chat*>::of(&Server::addDccChat),
329             getViewContainer(), &ViewContainer::addDccChat, Qt::QueuedConnection);
330     connect(this, &Server::serverLag, getViewContainer(), &ViewContainer::updateStatusBarLagLabel);
331     connect(this, &Server::tooLongLag, getViewContainer(), &ViewContainer::setStatusBarLagLabelTooLongLag);
332     connect(this, &Server::resetLag, getViewContainer(), &ViewContainer::resetStatusBarLagLabel);
333     connect(getOutputFilter(), &OutputFilter::showView, getViewContainer(), &ViewContainer::showView);
334     connect(getOutputFilter(), &OutputFilter::openKonsolePanel, getViewContainer(), &ViewContainer::addKonsolePanel);
335     connect(getOutputFilter(), &OutputFilter::openChannelList, this, &Server::requestOpenChannelListPanel);
336     connect(getOutputFilter(), &OutputFilter::closeDccPanel, getViewContainer(), &ViewContainer::closeDccPanel);
337     connect(getOutputFilter(), &OutputFilter::addDccPanel, getViewContainer(), &ViewContainer::addDccPanel);
338 
339     // Inputfilter - queued connections should be used for slots that have blocking UI
340     connect(&m_inputFilter, &InputFilter::addDccChat,
341             this, QOverload<const QString&, const QStringList&>::of(&Server::addDccChat), Qt::QueuedConnection);
342     connect(&m_inputFilter, &InputFilter::rejectDccChat,
343             this, &Server::rejectDccChat);
344     connect(&m_inputFilter, &InputFilter::startReverseDccChat,
345             this, &Server::startReverseDccChat);
346     connect(&m_inputFilter, &InputFilter::welcome, this, &Server::capCheckIgnored);
347     connect(&m_inputFilter, &InputFilter::welcome, this, &Server::connectionEstablished);
348     connect(&m_inputFilter, &InputFilter::notifyResponse, this, &Server::notifyResponse);
349     connect(&m_inputFilter, &InputFilter::startReverseDccSendTransfer,
350         this, &Server::startReverseDccSendTransfer);
351     connect(&m_inputFilter, &InputFilter::addDccGet,
352             this, &Server::addDccGet, Qt::QueuedConnection);
353     connect(&m_inputFilter, &InputFilter::resumeDccGetTransfer,
354         this, &Server::resumeDccGetTransfer);
355     connect(&m_inputFilter, &InputFilter::resumeDccSendTransfer,
356         this, &Server::resumeDccSendTransfer);
357     connect(&m_inputFilter, &InputFilter::rejectDccSendTransfer,
358         this, &Server::rejectDccSendTransfer);
359     connect(&m_inputFilter, &InputFilter::userhost,
360         this, &Server::userhost );
361     connect(&m_inputFilter, &InputFilter::topicAuthor,
362         this, &Server::setTopicAuthor );
363     connect(&m_inputFilter, &InputFilter::endOfWho,
364         this, &Server::endOfWho );
365     connect(&m_inputFilter, &InputFilter::endOfNames,
366         this, &Server::endOfNames );
367     connect(&m_inputFilter, &InputFilter::invitation,
368         this,&Server::invitation );
369     connect(&m_inputFilter, &InputFilter::addToChannelList,
370         this, &Server::addToChannelList);
371 
372     // Status View
373     connect(this, &Server::serverOnline, getStatusView(), &StatusPanel::serverOnline);
374 
375         // Scripts
376     connect(getOutputFilter(), &OutputFilter::launchScript,
377         konvApp->getScriptLauncher(), &ScriptLauncher::launchScript);
378     connect(konvApp->getScriptLauncher(), &ScriptLauncher::scriptNotFound,
379         this, &Server::scriptNotFound);
380     connect(konvApp->getScriptLauncher(), &ScriptLauncher::scriptExecutionError,
381         this, &Server::scriptExecutionError);
382 
383     connect(Preferences::self(), &Preferences::notifyListStarted,
384         this, &Server::notifyListStarted, Qt::QueuedConnection);
385 }
386 
getPort() const387 int Server::getPort() const
388 {
389     return getConnectionSettings().server().port();
390 }
391 
getLag() const392 int Server::getLag()  const
393 {
394     return m_currentLag;
395 }
396 
getAutoJoin() const397 bool Server::getAutoJoin()  const
398 {
399     return m_autoJoin;
400 }
401 
setAutoJoin(bool on)402 void Server::setAutoJoin(bool on)
403 {
404     m_autoJoin = on;
405 }
406 
preShellCommandExited(int exitCode,QProcess::ExitStatus exitStatus)407 void Server::preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus)
408 {
409     Q_UNUSED(exitCode)
410 
411     if (exitStatus == QProcess::NormalExit)
412         getStatusView()->appendServerMessage(i18n("Info"), i18n("Pre-shell command executed successfully!"));
413     else
414     {
415         QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString();
416         getStatusView()->appendServerMessage(i18n("Warning"), errorText);
417     }
418 
419     connectToIRCServer();
420     connectSignals();
421 }
422 
preShellCommandError(QProcess::ProcessError error)423 void Server::preShellCommandError(QProcess::ProcessError error)
424 {
425     Q_UNUSED(error)
426 
427     QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString();
428     getStatusView()->appendServerMessage(i18n("Warning"), errorText);
429 
430     connectToIRCServer();
431     connectSignals();
432 }
433 
connectToIRCServer()434 void Server::connectToIRCServer()
435 {
436     if (!isConnected() && !isConnecting())
437     {
438         if (m_sslErrorLock)
439         {
440             qCDebug(KONVERSATION_LOG) << "Refusing to connect while SSL lock from previous connection attempt is being held.";
441 
442             return;
443         }
444 
445         // Reenable check when it works reliably for all backends
446 //         if(Solid::Networking::status() != Solid::Networking::Connected)
447 //         {
448 //             updateConnectionState(Konversation::SSInvoluntarilyDisconnected);
449 //             return;
450 //         }
451 
452         updateConnectionState(Konversation::SSConnecting);
453 
454         m_ownIpByUserhost.clear();
455 
456         resetQueues();
457 
458         // This is needed to support server groups with mixed SSL and nonSSL servers
459         delete m_socket;
460         m_socket = nullptr;
461         if (m_referenceNicklist != getIdentity()->getNicknameList())
462             m_nickListModel->setStringList(getIdentity()->getNicknameList());
463         resetNickSelection();
464 
465         m_socket = new QSslSocket();
466         m_socket->setObjectName(QStringLiteral("serverSocket"));
467 
468 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
469         connect(m_socket, &QAbstractSocket::errorOccurred,
470 #else
471         connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
472 #endif
473                 this, &Server::broken);
474 
475         connect(m_socket, &QIODevice::readyRead, this, &Server::incoming);
476         connect(m_socket, &QAbstractSocket::disconnected, this, &Server::closed);
477         connect(m_socket, &QAbstractSocket::hostFound, this, &Server::hostFound);
478 
479         getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port %2)...",
480             getConnectionSettings().server().host(),
481             QString::number(getConnectionSettings().server().port())));
482 
483         if(getConnectionSettings().server().bypassProxy()) {
484             m_socket->setProxy(QNetworkProxy::NoProxy);
485         }
486 
487         // connect() will do a async lookup too
488         if (getConnectionSettings().server().SSLEnabled()
489             || getIdentity()->getAuthType() == QLatin1String("saslexternal")
490             || getIdentity()->getAuthType() == QLatin1String("pemclientcert"))
491         {
492             connect(m_socket, &QSslSocket::encrypted, this, &Server::socketConnected);
493             connect(m_socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
494                     this, &Server::sslError);
495 
496             if (getIdentity()->getAuthType() == QLatin1String("saslexternal")
497                 || getIdentity()->getAuthType() == QLatin1String("pemclientcert"))
498             {
499                 m_socket->setLocalCertificate(getIdentity()->getPemClientCertFile().toLocalFile());
500                 m_socket->setPrivateKey(getIdentity()->getPemClientCertFile().toLocalFile());
501             }
502 
503             m_socket->setProtocol(QSsl::SecureProtocols);
504             // QIODevice::Unbuffered, see m_socket->connectToHost() call below
505             m_socket->connectToHostEncrypted(getConnectionSettings().server().host(),
506                                              getConnectionSettings().server().port(),
507                                              (QIODevice::ReadWrite | QIODevice::Unbuffered));
508         }
509         else
510         {
511             connect(m_socket, &QAbstractSocket::connected, this, &Server::socketConnected);
512             // From KTcpSocket::connectToHost():
513             // There are enough layers of buffers between us and the network, and there is a quirk
514             // in QIODevice that can make it try to readData() twice per read() call if buffered and
515             // reaData() does not deliver enough data the first time. Like when the other side is
516             // simply not sending any more data...
517             // This can *apparently* lead to long delays sometimes which stalls applications.
518             // Do not want.
519             m_socket->connectToHost(getConnectionSettings().server().host(),
520                                     getConnectionSettings().server().port(),
521                                     (QIODevice::ReadWrite | QIODevice::Unbuffered));
522         }
523 
524         // set up the connection details
525         setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes);
526         // reset InputFilter (auto request info, /WHO request info)
527         m_inputFilter.reset();
528     }
529     else
530         qCDebug(KONVERSATION_LOG) << "connectToIRCServer() called while already connected: This should never happen. (" << (isConnecting() << 1) + isConnected() << ')';
531 }
532 
connectToIRCServerIn(uint delay)533 void Server::connectToIRCServerIn(uint delay)
534 {
535     m_delayedConnectTimer->setInterval(delay * 1000);
536     m_delayedConnectTimer->start();
537 
538     updateConnectionState(Konversation::SSScheduledToConnect);
539 }
540 
showSSLDialog()541 void Server::showSSLDialog()
542 {
543         //TODO
544         /*
545           SSLSocket* sslsocket = dynamic_cast<SSLSocket*>(m_socket);
546 
547           if (sslsocket) sslsocket->showInfoDialog();
548         */
549 }
550 
rebuildTargetPrefixMatcher()551 void Server::rebuildTargetPrefixMatcher()
552 {
553     m_targetMatcher.setPattern(QLatin1String("^([") + getServerNickPrefixes() + QLatin1String("]*)([") + getChannelTypes() + QLatin1String("])(.*)"));
554 }
555 
556 // set available channel types according to 005 RPL_ISUPPORT
setChannelTypes(const QString & pre)557 void Server::setChannelTypes(const QString &pre)
558 {
559     m_channelPrefixes = pre;
560     rebuildTargetPrefixMatcher();
561 
562     if (getConnectionSettings().reconnectCount() == 0) {
563         updateAutoJoin(m_connectionSettings.oneShotChannelList());
564     } else {
565         updateAutoJoin();
566     }
567 }
568 
getChannelTypes() const569 QString Server::getChannelTypes() const
570 {
571     return m_channelPrefixes;
572 }
573 
574 // set max number of channel modes with parameter according to 005 RPL_ISUPPORT
setModesCount(int count)575 void Server::setModesCount(int count)
576 {
577     m_modesCount = count;
578 }
579 
getModesCount() const580 int Server::getModesCount() const
581 {
582     return m_modesCount;
583 }
584 
585 // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp)
setPrefixes(const QString & modes,const QString & prefixes)586 void Server::setPrefixes(const QString &modes, const QString& prefixes)
587 {
588     // NOTE: serverModes is QString(), if server did not supply the
589     // modes which relates to the network's nick-prefixes
590     m_serverNickPrefixModes = modes;
591     m_serverNickPrefixes = prefixes;
592     rebuildTargetPrefixMatcher();
593 }
594 
getServerNickPrefixes() const595 QString Server::getServerNickPrefixes() const
596 {
597     return m_serverNickPrefixes;
598 }
599 
setChanModes(const QString & modes)600 void Server::setChanModes(const QString& modes)
601 {
602     QStringList abcd = modes.split(QLatin1Char(','));
603     m_banAddressListModes = abcd.value(0);
604 }
605 
606 // return a nickname without possible mode character at the beginning
mangleNicknameWithModes(QString & nickname,bool & isAdmin,bool & isOwner,bool & isOp,bool & isHalfop,bool & hasVoice)607 void Server::mangleNicknameWithModes(QString& nickname,bool& isAdmin,bool& isOwner,
608 bool& isOp,bool& isHalfop,bool& hasVoice)
609 {
610     isAdmin = false;
611     isOwner = false;
612     isOp = false;
613     isHalfop = false;
614     hasVoice = false;
615 
616     int modeIndex;
617 
618     if (nickname.isEmpty()) return;
619 
620     while ((modeIndex = m_serverNickPrefixes.indexOf(nickname[0])) != -1)
621     {
622         if(nickname.isEmpty())
623             return;
624         nickname.remove(0, 1);
625         // cut off the prefix
626         bool recognisedMode = false;
627         // determine, whether status is like op or like voice
628         while(modeIndex < m_serverNickPrefixes.length() && !recognisedMode)
629         {
630             switch(m_serverNickPrefixes[modeIndex].toLatin1())
631             {
632                 case '*':                         // admin (EUIRC)
633                 {
634                     isAdmin = true;
635                     recognisedMode = true;
636                     break;
637                 }
638                 case '&':                         // admin (unrealircd)
639                 {
640                     isAdmin = true;
641                     recognisedMode = true;
642                     break;
643                 }
644                 case '!':                         // channel owner (RFC2811)
645                 {
646                     isOwner = true;
647                     recognisedMode = true;
648                     break;
649                 }
650                 case '~':                         // channel owner (unrealircd)
651                 {
652                     isOwner = true;
653                     recognisedMode = true;
654                     break;
655                 }
656                 case '@':                         // channel operator (RFC1459)
657                 {
658                     isOp = true;
659                     recognisedMode = true;
660                     break;
661                 }
662                 case '%':                         // halfop
663                 {
664                     isHalfop = true;
665                     recognisedMode = true;
666                     break;
667                 }
668                 case '+':                         // voiced (RFC1459)
669                 {
670                     hasVoice = true;
671                     recognisedMode = true;
672                     break;
673                 }
674                 default:
675                 {
676                     ++modeIndex;
677                     break;
678                 }
679             }                                     //switch to recognise the mode.
680         }                                         // loop through the modes to find one recognised
681     }                                             // loop through the name
682 }
683 
hostFound()684 void Server::hostFound()
685 {
686     getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting..."));
687 }
688 
socketConnected()689 void Server::socketConnected()
690 {
691     Q_EMIT sslConnected(this);
692     getConnectionSettings().setReconnectCount(0);
693 
694     requestAvailableCapabilies();
695 
696     QStringList ql;
697 
698     if (getIdentity() && getIdentity()->getAuthType() == QLatin1String("serverpw")
699         && !getIdentity()->getAuthPassword().isEmpty())
700     {
701         ql << QStringLiteral("PASS :") + getIdentity()->getAuthPassword();
702     }
703     else if (!getConnectionSettings().server().password().isEmpty())
704         ql << QStringLiteral("PASS :") + getConnectionSettings().server().password();
705 
706     ql << QStringLiteral("NICK ") + getNickname();
707     ql << QStringLiteral("USER ") + getIdentity()->getIdent() + QStringLiteral(" 8 * :") /* 8 = +i; 4 = +w */ +  getIdentity()->getRealName();
708 
709     queueList(ql, HighPriority);
710 
711     connect(this, &Server::nicknameChanged, getStatusView(), &StatusPanel::setNickname);
712     setNickname(getNickname());
713 }
714 
requestAvailableCapabilies()715 void Server::requestAvailableCapabilies ()
716 {
717     m_capRequested = 0;
718     m_capAnswered = 0;
719     m_capEndDelayed = false;
720     m_capabilities = NoCapabilies;
721     getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server..."));
722     m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true);
723     queue(QStringLiteral("CAP LS 302"), HighPriority);
724 }
725 
capInitiateNegotiation(const QString & availableCaps)726 void Server::capInitiateNegotiation(const QString &availableCaps)
727 {
728     QStringList requestCaps;
729 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
730     const QStringList capsList = availableCaps.split(QLatin1Char(' '), QString::SkipEmptyParts);
731 #else
732     const QStringList capsList = availableCaps.split(QLatin1Char(' '), Qt::SkipEmptyParts);
733 #endif
734     QStringList nameValue;
735 
736     for (const QString &cap : capsList) {
737         nameValue = cap.split(QLatin1Char('='));
738 
739         if (nameValue.isEmpty())
740             continue;
741 
742         if(nameValue.at(0) == QLatin1String("sasl"))
743         {
744             QString authCommand;
745 
746             if (getIdentity()) {
747                 // A username is optional SASL EXTERNAL and a client cert substitutes
748                 // for the password.
749                 if (getIdentity()->getAuthType() == QLatin1String("saslexternal")) {
750                     authCommand = QStringLiteral("EXTERNAL");
751                 // PLAIN on the other hand requires both.
752                 } else if (getIdentity()->getAuthType() == QLatin1String("saslplain")
753                     && !getIdentity()->getSaslAccount().isEmpty()
754                     && !getIdentity()->getAuthPassword().isEmpty()) {
755                     authCommand = QStringLiteral("PLAIN");
756                 }
757             }
758 
759             if(!authCommand.isEmpty())
760             {
761                 QStringList supportedSaslTypes;
762                 if(nameValue.size() > 1)
763                     supportedSaslTypes = nameValue.at(1).split(QLatin1Char(','));
764 
765                 supportedSaslTypes.removeDuplicates();
766 
767                 if(!supportedSaslTypes.isEmpty() && !supportedSaslTypes.contains(authCommand))
768                     getStatusView()->appendServerMessage(i18n("Error"), i18n("Server does not support %1 as SASL authentication mechanism, skipping SASL authentication.", authCommand));
769                 else
770                     requestCaps.append (QStringLiteral("sasl"));
771             }
772         }
773         else if(m_capabilityNames.contains(nameValue.at(0)))
774         {
775             requestCaps.append (nameValue.at(0));
776         }
777 
778         // HACK: twitch.tv's IRC server doesn't handle WHO so
779         // let's disable all WHO requests for servers that has
780         // twitch.tv capabilities
781         if (nameValue.at(0).startsWith(QLatin1String("twitch.tv"))) {
782             m_whoRequestsDisabled = true;
783         }
784     }
785 
786     if(!requestCaps.isEmpty())
787     {
788         QString capsString = requestCaps.join(QLatin1Char(' '));
789         getStatusView()->appendServerMessage(i18n("Info"),i18n("Requesting capabilities: %1", capsString));
790         queue(QStringLiteral("CAP REQ :") + capsString, HighPriority);
791         m_capRequested++;
792     }
793 }
794 
capReply()795 void Server::capReply()
796 {
797     m_capAnswered++;
798 }
799 
capEndNegotiation()800 void Server::capEndNegotiation()
801 {
802     if(m_capRequested == m_capAnswered)
803     {
804         getStatusView()->appendServerMessage(i18n("Info"),i18n("Closing capabilities negotiation."));
805         queue(QStringLiteral("CAP END"), HighPriority);
806     }
807 }
808 
capCheckIgnored()809 void Server::capCheckIgnored()
810 {
811     if (m_capRequested && !m_capAnswered)
812         getStatusView()->appendServerMessage(i18n("Error"), i18n("Capabilities negotiation failed: Appears not supported by server."));
813 }
814 
capAcknowledged(const QString & name,Server::CapModifiers modifiers)815 void Server::capAcknowledged(const QString& name, Server::CapModifiers modifiers)
816 {
817     if (name == QLatin1String("sasl") && modifiers == Server::NoModifiers)
818     {
819         const QString &authCommand = (getIdentity()->getAuthType() == QLatin1String("saslexternal")) ?
820             QStringLiteral("EXTERNAL") : QStringLiteral("PLAIN");
821 
822         getStatusView()->appendServerMessage(i18n("Info"), i18n("SASL capability acknowledged by server, attempting SASL %1 authentication...", authCommand));
823 
824         sendAuthenticate(authCommand);
825 
826         m_capEndDelayed = true;
827     }
828 
829     m_capabilities |= m_capabilityNames.value(name);
830 }
831 
capDenied(const QString & name)832 void Server::capDenied(const QString& name)
833 {
834     if (name == QLatin1String("sasl"))
835         getStatusView()->appendServerMessage(i18n("Error"), i18n("SASL capability denied or not supported by server."));
836 }
837 
capDel(const QString & unavailableCaps)838 void Server::capDel(const QString &unavailableCaps)
839 {
840 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
841     const QStringList capsList = unavailableCaps.split(QLatin1Char(' '), QString::SkipEmptyParts);
842 #else
843     const QStringList capsList = unavailableCaps.split(QLatin1Char(' '), Qt::SkipEmptyParts);
844 #endif
845 
846     for (const QString &capString : qAsConst(capsList))
847     {
848         if (m_capabilityNames.contains(capString))
849             m_capabilities &= ~(m_capabilityNames.value(capString));
850     }
851 }
852 
registerWithServices()853 void Server::registerWithServices()
854 {
855     if (!getIdentity())
856         return;
857 
858     NickInfoPtr nickInfo = getNickInfo(getNickname());
859     if (nickInfo && nickInfo->isIdentified())
860         return;
861 
862     if (getIdentity()->getAuthType() == QLatin1String("nickserv"))
863     {
864         if (!getIdentity()->getNickservNickname().isEmpty()
865             && !getIdentity()->getNickservCommand().isEmpty()
866             && !getIdentity()->getAuthPassword().isEmpty())
867         {
868             queue(QStringLiteral("PRIVMSG ")+getIdentity()->getNickservNickname()+QStringLiteral(" :")+getIdentity()->getNickservCommand()+QLatin1Char(' ')+getIdentity()->getAuthPassword(), HighPriority);
869         }
870     }
871     else if (getIdentity()->getAuthType() == QLatin1String("saslplain"))
872     {
873         QString authString = getIdentity()->getSaslAccount();
874         authString.append(QChar(QChar::Null));
875         authString.append(getIdentity()->getSaslAccount());
876         authString.append(QChar(QChar::Null));
877         authString.append(getIdentity()->getAuthPassword());
878 
879         sendAuthenticate(QLatin1String(authString.toLatin1().toBase64()));
880     }
881     else if (getIdentity()->getAuthType() == QLatin1String("saslexternal"))
882     {
883         QString authString = getIdentity()->getSaslAccount();
884 
885         // An account name is optional with SASL EXTERNAL.
886         if (!authString.isEmpty()) {
887             authString.append(QChar(QChar::Null));
888             authString.append(getIdentity()->getSaslAccount());
889         }
890 
891         sendAuthenticate(QLatin1String(authString.toLatin1().toBase64()));
892     }
893 }
894 
sendAuthenticate(const QString & message)895 void Server::sendAuthenticate(const QString& message)
896 {
897     m_lastAuthenticateCommand = message;
898 
899     if (message.isEmpty()) {
900         queue(QStringLiteral("AUTHENTICATE +"), HighPriority);
901     } else {
902         queue(QStringLiteral("AUTHENTICATE ") + message, HighPriority);
903     }
904 }
905 
broken(QAbstractSocket::SocketError error)906 void Server::broken(QAbstractSocket::SocketError error)
907 {
908     Q_UNUSED(error)
909 
910     qCDebug(KONVERSATION_LOG) << "Connection broken with state" << m_connectionState << "and error:" << m_socket->errorString();
911 
912     m_socket->blockSignals(true);
913 
914     resetQueues();
915 
916     m_notifyTimer.stop();
917     m_pingSendTimer.stop();
918     m_pingResponseTimer.stop();
919     m_inputFilter.setLagMeasuring(false);
920     m_currentLag = -1;
921 
922     purgeData();
923 
924     Q_EMIT resetLag(this);
925     Q_EMIT nicksNowOnline(this, QStringList(), true);
926     m_prevISONList.clear();
927 
928     updateAutoJoin();
929 
930 
931     if (m_sslErrorLock)
932     {
933         // We got disconnected while dealing with an SSL error, e.g. due to the
934         // user taking their time on dealing with the error dialog. Since auto-
935         // reconnecting makes no sense in this situation, let's pass it off as a
936         // deliberate disconnect. sslError() will either kick off a reconnect, or
937         // reaffirm this.
938 
939         getStatusView()->appendServerMessage(i18n("SSL Connection Error"),
940             i18n("Connection to server %1 (port %2) lost while waiting for user response to an SSL error. "
941                  "Will automatically reconnect if error is ignored.",
942                  getConnectionSettings().server().host(),
943                  QString::number(getConnectionSettings().server().port())));
944 
945         updateConnectionState(SSDeliberatelyDisconnected);
946     }
947     else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected)
948     {
949         if (m_reconnectImmediately)
950         {
951             m_reconnectImmediately = false;
952 
953             QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection);
954         }
955     }
956     else
957     {
958         Application::instance()->notificationHandler()->connectionFailure(getStatusView(), getServerName());
959 
960         QString error = i18n("Connection to server %1 (port %2) lost: %3.",
961             getConnectionSettings().server().host(),
962             QString::number(getConnectionSettings().server().port()),
963             m_socket->errorString());
964 
965         getStatusView()->appendServerMessage(i18n("Error"), error);
966 
967         updateConnectionState(Konversation::SSInvoluntarilyDisconnected);
968     }
969 
970     // HACK Only show one nick change dialog at connection time.
971     // This hack is a bit nasty as it assumes that the only QInputDialog
972     // child of the statusview will be the nick change dialog.
973     if (getStatusView())
974     {
975         auto* nickChangeDialog = getStatusView()->findChild<QInputDialog*>();
976 
977         if (nickChangeDialog) nickChangeDialog->reject();
978     }
979 
980 }
981 
sslError(const QList<QSslError> & errors)982 void Server::sslError(const QList<QSslError> &errors)
983 {
984     // We have to explicitly grab the socket we got the error from,
985     // lest we might end up calling ignoreSslErrors() on a different
986     // socket later if m_socket has started pointing at something
987     // else.
988     QPointer<QSslSocket> socket = qobject_cast<QSslSocket *>(QObject::sender());
989 
990     m_sslErrorLock = true;
991     KSslErrorUiData uiData(socket);
992     bool ignoreSslErrors = KIO::SslUi::askIgnoreSslErrors(uiData, KIO::SslUi::RecallAndStoreRules);
993     m_sslErrorLock = false;
994 
995     // The dialog-based user interaction above may take an undefined amount
996     // of time, and anything could happen to the socket in that span of time.
997     // If it was destroyed, let's not do anything and bail out.
998     if (!socket)
999     {
1000         qCDebug(KONVERSATION_LOG) << "Socket was destroyed while waiting for user interaction.";
1001 
1002         return;
1003     }
1004 
1005     // Ask the user if he wants to ignore the errors.
1006     if (ignoreSslErrors)
1007     {
1008         // Show a warning in the chat window that the SSL certificate failed the authenticity check.
1009         QString error = i18n("The SSL certificate for the server %1 (port %2) failed the authenticity check.",
1010             getConnectionSettings().server().host(),
1011             QString::number(getConnectionSettings().server().port()));
1012 
1013         getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error);
1014 
1015         // We may have gotten disconnected while waiting for user response and have
1016         // to reconnect.
1017         if (isConnecting())
1018         {
1019             // The user has chosen to ignore SSL errors.
1020             socket->ignoreSslErrors();
1021         }
1022         else
1023         {
1024             // QueuedConnection is vital here, otherwise we're deleting the socket
1025             // in a slot connected to one of its signals (connectToIRCServer deletes
1026             // any old socket) and crash.
1027             QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection);
1028         }
1029     }
1030     else
1031     {
1032         // Don't auto-reconnect if the user chose to ignore the SSL errors --
1033         // treat it as a deliberate disconnect.
1034         updateConnectionState(Konversation::SSDeliberatelyDisconnected);
1035 
1036         QString errorReason;
1037 
1038         for (const QSslError& error : errors) {
1039             errorReason += error.errorString() + QLatin1Char(' ');
1040         }
1041 
1042         QString error = i18n("Could not connect to %1 (port %2) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3",
1043             getConnectionSettings().server().host(),
1044             QString::number(getConnectionSettings().server().port()),
1045             errorReason);
1046 
1047         getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error);
1048 
1049         Q_EMIT sslInitFailure();
1050     }
1051 }
1052 
1053 // Will be called from InputFilter as soon as the Welcome message was received
connectionEstablished(const QString & ownHost)1054 void Server::connectionEstablished(const QString& ownHost)
1055 {
1056     // Some servers don't include the userhost in RPL_WELCOME, so we
1057     // need to use RPL_USERHOST to get ahold of our IP later on
1058     if (!ownHost.isEmpty())
1059         QHostInfo::lookupHost(ownHost, this, &Server::gotOwnResolvedHostByWelcome);
1060 
1061     updateConnectionState(Konversation::SSConnected);
1062 
1063     // Make a helper object to build ISON (notify) list.
1064     // TODO: Give the object a kick to get it started?
1065     delete m_serverISON;
1066     m_serverISON = new ServerISON(this);
1067 
1068     // get first notify very early
1069     startNotifyTimer(1000);
1070 
1071     // Register with services
1072     if (getIdentity() && getIdentity()->getAuthType() == QLatin1String("nickserv"))
1073         registerWithServices();
1074 
1075     // get own ip by userhost
1076     requestUserhost(getNickname());
1077 
1078     // Start the PINGPONG match
1079     m_pingSendTimer.start(1000 /*1 sec*/);
1080 
1081     // Recreate away state if we were set away prior to a reconnect.
1082     if (m_away)
1083     {
1084         // Correct server's beliefs about its away state.
1085         m_away = false;
1086         requestAway(m_awayReason);
1087     }
1088 }
1089 
1090 //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage
getKeyForRecipient(const QString & recipient) const1091 QByteArray Server::getKeyForRecipient(const QString& recipient) const
1092 {
1093     return m_keyHash[recipient.toLower()];
1094 }
1095 
setKeyForRecipient(const QString & recipient,const QByteArray & key)1096 void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key)
1097 {
1098     m_keyHash[recipient.toLower()] = key;
1099 }
1100 
gotOwnResolvedHostByWelcome(const QHostInfo & res)1101 void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res)
1102 {
1103     if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty())
1104         m_ownIpByWelcome = res.addresses().first().toString();
1105     else
1106         qCDebug(KONVERSATION_LOG) << "Got error: " << res.errorString();
1107 }
1108 
isSocketConnected() const1109 bool Server::isSocketConnected() const
1110 {
1111     if (!m_socket) return false;
1112 
1113     return (m_socket->state() == QAbstractSocket::ConnectedState);
1114 }
1115 
updateConnectionState(Konversation::ConnectionState state)1116 void Server::updateConnectionState(Konversation::ConnectionState state)
1117 {
1118     if (state != m_connectionState)
1119     {
1120         m_connectionState = state;
1121 
1122         if (m_connectionState == Konversation::SSConnected)
1123             Q_EMIT serverOnline(true);
1124         else if (m_connectionState != Konversation::SSConnecting)
1125             Q_EMIT serverOnline(false);
1126 
1127        Q_EMIT connectionStateChanged(this, state);
1128     }
1129 }
1130 
reconnectServer(const QString & quitMessage)1131 void Server::reconnectServer(const QString& quitMessage)
1132 {
1133     if (isConnecting() || isSocketConnected())
1134     {
1135         m_reconnectImmediately = true;
1136 
1137         quitServer(quitMessage);
1138     }
1139     else
1140         QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection);
1141 }
1142 
disconnectServer(const QString & quitMessage)1143 void Server::disconnectServer(const QString& quitMessage)
1144 {
1145     getConnectionSettings().setReconnectCount(0);
1146 
1147     if (isScheduledToConnect())
1148     {
1149         m_delayedConnectTimer->stop();
1150         getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted."));
1151     }
1152 
1153     if (isSocketConnected()) quitServer(quitMessage);
1154 }
1155 
quitServer(const QString & quitMessage)1156 void Server::quitServer(const QString& quitMessage)
1157 {
1158     // Make clear this is deliberate even if the QUIT never actually goes through the queue
1159     // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for
1160     // a QUIT).
1161     updateConnectionState(Konversation::SSDeliberatelyDisconnected);
1162 
1163     if (!m_socket) return;
1164 
1165     QString toServer = QStringLiteral("QUIT :");
1166 
1167     if (quitMessage.isEmpty())
1168         toServer += getIdentity()->getQuitReason();
1169     else
1170         toServer += quitMessage;
1171 
1172     queue(toServer, HighPriority);
1173 
1174     flushQueues();
1175     m_socket->flush();
1176 
1177     // Close the socket to allow a dead connection to be reconnected before the socket timeout.
1178     m_socket->close();
1179 
1180     getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port %2).",
1181         getConnectionSettings().server().host(),
1182         QString::number(getConnectionSettings().server().port())));
1183 }
1184 
notifyAction(const QString & nick)1185 void Server::notifyAction(const QString& nick)
1186 {
1187     QString out(Preferences::self()->notifyDoubleClickAction());
1188 
1189     OutputFilter::replaceAliases(out);
1190 
1191     // parse wildcards (toParse,nickname,channelName,nickList,parameter)
1192     out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString());
1193 
1194     // Send all strings, one after another
1195 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1196     const QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts);
1197 #else
1198     const QStringList outList = out.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
1199 #endif
1200     for (const QString& out : outList) {
1201         Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), out, QString());
1202         queue(result.toServer);
1203     }                                             // endfor
1204 }
1205 
notifyResponse(const QString & nicksOnline)1206 void Server::notifyResponse(const QString& nicksOnline)
1207 {
1208     bool nicksOnlineChanged = false;
1209 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1210     const QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts);
1211 #else
1212     const QStringList actualList = nicksOnline.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1213 #endif
1214     QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' ');
1215     QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QLatin1Char(' '))) + QLatin1Char(' ');
1216 
1217     //Are any nicks gone offline
1218     for (const QString& nick : qAsConst(m_prevISONList)) {
1219         if (!lcActual.contains(QLatin1Char(' ') + nick + QLatin1Char(' '), Qt::CaseInsensitive)) {
1220             setNickOffline(nick);
1221             nicksOnlineChanged = true;
1222         }
1223     }
1224 
1225     //Are any nicks gone online
1226     for (const QString& nick : actualList) {
1227         if (!lcPrevISON.contains(QLatin1Char(' ') + nick + QLatin1Char(' '), Qt::CaseInsensitive)) {
1228             setWatchedNickOnline(nick);
1229             nicksOnlineChanged = true;
1230         }
1231     }
1232 
1233     // Note: The list emitted in this signal *does* include nicks in joined channels.
1234     Q_EMIT nicksNowOnline(this, actualList, nicksOnlineChanged);
1235 
1236     m_prevISONList = actualList;
1237 
1238     // Next round
1239     startNotifyTimer();
1240 }
1241 
notifyListStarted(int serverGroupId)1242 void Server::notifyListStarted(int serverGroupId)
1243 {
1244     if (getServerGroup())
1245         if (getServerGroup()->id() == serverGroupId)
1246             startNotifyTimer(1000);
1247 }
1248 
startNotifyTimer(int msec)1249 void Server::startNotifyTimer(int msec)
1250 {
1251     // make sure the timer gets started properly in case we have reconnected
1252     m_notifyTimer.stop();
1253 
1254     if (Preferences::self()->useNotify())
1255     {
1256         if (msec == 0)
1257             msec = Preferences::self()->notifyDelay() * 1000;
1258 
1259         m_notifyTimer.start(msec);
1260     }
1261 }
1262 
notifyTimeout()1263 void Server::notifyTimeout()
1264 {
1265     // Notify delay time is over, send ISON request if desired
1266     if (Preferences::self()->useNotify())
1267     {
1268         // But only if there actually are nicks in the notify list
1269         QString list = getISONListString();
1270 
1271         if (!list.isEmpty())
1272             queue(QStringLiteral("ISON ") + list, LowPriority);
1273     }
1274 }
1275 
autoCommandsAndChannels()1276 void Server::autoCommandsAndChannels()
1277 {
1278     if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty())
1279     {
1280         QString connectCommands = getServerGroup()->connectCommands();
1281 
1282         if (!getNickname().isEmpty())
1283             connectCommands.replace(QStringLiteral("%nick"), getNickname());
1284 
1285 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1286         const QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts);
1287 #else
1288         const QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), Qt::SkipEmptyParts);
1289 #endif
1290 
1291         for (QString output : connectCommandsList) {
1292             output = output.simplified();
1293             OutputFilter::replaceAliases(output);
1294             Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString());
1295             queue(result.toServer);
1296         }
1297     }
1298 
1299     if (getAutoJoin())
1300     {
1301         for (const QString& command : qAsConst(m_autoJoinCommands)) {
1302             queue(command);
1303         }
1304     }
1305 
1306     if (!m_connectionSettings.oneShotChannelList().isEmpty())
1307     {
1308         const QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList());
1309         for (const QString& join : oneShotJoin) {
1310             queue(join);
1311         }
1312         m_connectionSettings.clearOneShotChannelList();
1313     }
1314 }
1315 
1316 /** Create a set of indices into the nickname list of the current identity based on the current nickname.
1317  *
1318  * The index list is only used if the current nickname is not available. If the nickname is in the identity,
1319  * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1.
1320  */
resetNickSelection()1321 void Server::resetNickSelection()
1322 {
1323     m_nickIndices.clear();
1324 
1325     // For equivalence testing in case the identity gets changed underneath us.
1326     m_referenceNicklist = getIdentity()->getNicknameList();
1327 
1328     for (int i = 0; i < m_referenceNicklist.length(); ++i) {
1329         // Pointless to include the nick we're already going to use.
1330         if (m_referenceNicklist.at(i) != getNickname()) {
1331             m_nickIndices.append(i);
1332         }
1333     }
1334 }
1335 
getNextNickname()1336 QString Server::getNextNickname()
1337 {
1338      //if the identity changed underneath us (likely impossible), start over
1339     if (m_referenceNicklist != getIdentity()->getNicknameList())
1340         resetNickSelection();
1341 
1342     QString newNick;
1343 
1344     if (!m_nickIndices.isEmpty())
1345     {
1346         newNick = getIdentity()->getNickname(m_nickIndices.takeFirst());
1347     }
1348     else
1349     {
1350         QString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:", getIdentity()->getName(), getDisplayName());
1351         newNick = QInputDialog::getText(getStatusView(), i18n("Nickname error"), inputText);
1352     }
1353     return newNick;
1354 }
1355 
processIncomingData()1356 void Server::processIncomingData()
1357 {
1358     m_incomingTimer.stop();
1359 
1360     if (!m_inputBuffer.isEmpty() && !m_processingIncoming)
1361     {
1362         m_processingIncoming = true;
1363         QString front(m_inputBuffer.front());
1364         m_inputBuffer.pop_front();
1365         m_inputFilter.parseLine(front);
1366         m_processingIncoming = false;
1367 
1368         if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0);
1369     }
1370 }
1371 
incoming()1372 void Server::incoming()
1373 {
1374     //if (getConnectionSettings().server().SSLEnabled())
1375     //    Q_EMIT sslConnected(this);
1376 
1377 
1378     //if (len <= 0 && getConnectionSettings().server().SSLEnabled())
1379     //    return;
1380 
1381     // split buffer to lines
1382     QList<QByteArray> bufferLines;
1383     while (m_socket->bytesAvailable())
1384     {
1385         m_lineBuffer += m_socket->readLine();
1386 
1387         if(m_lineBuffer.endsWith('\n') || m_lineBuffer.endsWith('\r'))
1388         {
1389             //remove \n blowfish doesn't like it
1390             int i = m_lineBuffer.size()-1;
1391             while (i >= 0 && (m_lineBuffer[i]=='\n' || m_lineBuffer[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r?
1392             {
1393                 i--;
1394             }
1395             m_lineBuffer.truncate(i+1);
1396 
1397             if (!m_lineBuffer.isEmpty())
1398                 bufferLines.append(m_lineBuffer);
1399 
1400             m_lineBuffer.clear();
1401         }
1402     }
1403 
1404     while(!bufferLines.isEmpty())
1405     {
1406         // Pre parsing is needed in case encryption/decryption is needed
1407         // BEGIN set channel encoding if specified
1408         QString senderNick;
1409         bool isServerMessage = false;
1410         QString channelKey;
1411         QTextCodec* codec = getIdentity()->getCodec();
1412         QByteArray first = bufferLines.first();
1413 
1414         bufferLines.removeFirst();
1415 
1416 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1417         QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), QString::SkipEmptyParts);
1418 #else
1419         QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), Qt::SkipEmptyParts);
1420 #endif
1421 
1422         if (lineSplit.count() >= 1)
1423         {
1424             if (lineSplit[0][0] == QLatin1Char(':'))          // does this message have a prefix?
1425             {
1426                 if(!lineSplit[0].contains(QLatin1Char('!'))) // is this a server(global) message?
1427                     isServerMessage = true;
1428                 else
1429                     senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf(QLatin1Char('!'))-1);
1430 
1431                 lineSplit.removeFirst();          // remove prefix
1432             }
1433         }
1434 
1435         if (lineSplit.isEmpty())
1436             continue;
1437 
1438         // BEGIN pre-parse to know where the message belongs to
1439         QString command = lineSplit[0].toLower();
1440         if( isServerMessage )
1441         {
1442             if( lineSplit.count() >= 3 )
1443             {
1444                 if( command == QLatin1String("332") )            // RPL_TOPIC
1445                     channelKey = lineSplit[2];
1446                 if( command == QLatin1String("372") )            // RPL_MOTD
1447                     channelKey = QStringLiteral(":server");
1448             }
1449         }
1450         else                                      // NOT a global message
1451         {
1452             if( lineSplit.count() >= 2 )
1453             {
1454                 // query
1455                 if( ( command == QLatin1String("privmsg") ||
1456                     command == QLatin1String("notice")  ) &&
1457                     lineSplit[1] == getNickname() )
1458                 {
1459                     channelKey = senderNick;
1460                 }
1461                 // channel message
1462                 else if( command == QLatin1String("privmsg") ||
1463                     command == QLatin1String("notice")  ||
1464                     command == QLatin1String("join")    ||
1465                     command == QLatin1String("kick")    ||
1466                     command == QLatin1String("part")    ||
1467                     command == QLatin1String("topic")   )
1468                 {
1469                     channelKey = lineSplit[1];
1470                 }
1471             }
1472         }
1473         // END pre-parse to know where the message belongs to
1474         // Decrypt if necessary
1475 
1476         //send to raw log before decryption
1477         if (m_rawLog)
1478             m_rawLog->appendRaw(RawLog::Inbound, first);
1479 
1480         #ifdef HAVE_QCA2
1481         QByteArray cKey = getKeyForRecipient(channelKey);
1482         if(!cKey.isEmpty())
1483         {
1484             if(command == QLatin1String("privmsg"))
1485             {
1486                 //only send encrypted text to decrypter
1487                 int index = first.indexOf(":",first.indexOf(":")+1);
1488                 if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with +
1489                     ++index;
1490                 QByteArray backup = first.mid(0,index+1);
1491 
1492                 if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey))
1493                     first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1));
1494                 else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey))
1495                     first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1));
1496 
1497                 first.prepend(backup);
1498             }
1499             else if(command == QLatin1String("332") || command == QLatin1String("topic"))
1500             {
1501                 //only send encrypted text to decrypter
1502                 int index = first.indexOf(":",first.indexOf(":")+1);
1503                 QByteArray backup = first.mid(0,index+1);
1504 
1505                 if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey))
1506                     first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1));
1507                 else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey))
1508                     first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1));
1509 
1510                 first.prepend(backup);
1511             }
1512         }
1513         #endif
1514         bool isUtf8 = Konversation::isUtf8(first);
1515 
1516         QString encoded;
1517 
1518         if (isUtf8)
1519             encoded = QString::fromUtf8(first);
1520         else
1521         {
1522             // check setting
1523             QString channelEncoding;
1524             if( !channelKey.isEmpty() )
1525             {
1526                 if(getServerGroup())
1527                     channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey);
1528                 else
1529                     channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey);
1530             }
1531             // END set channel encoding if specified
1532 
1533             if( !channelEncoding.isEmpty() )
1534                 codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding);
1535 
1536             // if channel encoding is utf-8 and the string is definitely not utf-8
1537             // then try latin-1
1538             if (codec->mibEnum() == 106)
1539                 codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ );
1540 
1541             encoded = codec->toUnicode(first);
1542         }
1543 
1544         // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove
1545         // these here to avoid fatal errors encountered in QText* and the event loop pro-
1546         // cessing.
1547         sterilizeUnicode(encoded);
1548 
1549         if (!encoded.isEmpty())
1550             m_inputBuffer << encoded;
1551 
1552         //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number.
1553         //m_bytesReceived+=m_inputBuffer.back().length();
1554     }
1555 
1556     if( !m_incomingTimer.isActive() && !m_processingIncoming )
1557         m_incomingTimer.start(0);
1558 }
1559 
1560 /** Calculate how long this message premable will be.
1561 
1562     This is necessary because the irc server will clip messages so that the
1563     client receives a maximum of 512 bytes at once.
1564 */
getPreLength(const QString & command,const QString & dest) const1565 int Server::getPreLength(const QString& command, const QString& dest) const
1566 {
1567     NickInfoPtr info = getNickInfo(getNickname());
1568     int hostMaskLength = 0;
1569 
1570     if(info)
1571         hostMaskLength = info->getHostmask().length();
1572 
1573     //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it
1574 
1575     //<colon>$nickname<!>$hostmask<space>$command<space>$destination<space><colon>$message<cr><lf>
1576     int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length());
1577 
1578     return x;
1579 }
1580 
1581 //Commands greater than 1 have localizeable text
1582 static QStringList outcmds = QStringList {
1583     QStringLiteral("WHO"),     // 0
1584     QStringLiteral("QUIT"),    // 1
1585     QStringLiteral("PRIVMSG"), // 2
1586     QStringLiteral("NOTICE"),  // 3
1587     QStringLiteral("KICK"),    // 4
1588     QStringLiteral("PART"),    // 5
1589     QStringLiteral("TOPIC"),   // 6
1590 };
1591 
_send_internal(QString outputLine)1592 int Server::_send_internal(QString outputLine)
1593 {
1594 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1595     QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), QString::SkipEmptyParts);
1596 #else
1597     QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1598 #endif
1599 
1600     int outboundCommand = -1;
1601     if (!outputLineSplit.isEmpty()) {
1602         //Lets cache the uppercase command so we don't miss or reiterate too much
1603         outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper());
1604     }
1605 
1606     if (outputLine.at(outputLine.length()-1) == QLatin1Char('\n'))
1607     {
1608         qCDebug(KONVERSATION_LOG) << "found \\n on " << outboundCommand;
1609         outputLine.resize(outputLine.length()-1);
1610     }
1611 
1612     // remember the first arg of /WHO to identify responses
1613     if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO"
1614         m_inputFilter.addWhoRequest(outputLineSplit[1]);
1615     else if (outboundCommand == 1) //"QUIT"
1616         updateConnectionState(Konversation::SSDeliberatelyDisconnected);
1617 
1618     // set channel encoding if specified
1619     QString channelCodecName;
1620 
1621     //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message
1622     if (outputLineSplit.count() > 2 && outboundCommand > 1)
1623     {
1624         if(getServerGroup()) // if we're connecting via a servergroup
1625             channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]);
1626         else //if we're connecting to a server manually
1627             channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]);
1628     }
1629     QTextCodec* codec = nullptr;
1630     if (channelCodecName.isEmpty())
1631         codec = getIdentity()->getCodec();
1632     else
1633         codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName);
1634 
1635     // Some codecs don't work with a negative value. This is a bug in Qt 3.
1636     // ex.: JIS7, eucJP, SJIS
1637     //int outlen=-1;
1638 
1639     //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands
1640     QByteArray encoded = outputLine.toUtf8();
1641     if(codec)
1642         encoded = codec->fromUnicode(outputLine);
1643 
1644     #ifdef HAVE_QCA2
1645     QByteArray cipherKey;
1646     if (outputLineSplit.count() > 2 && outboundCommand > 1)
1647         cipherKey = getKeyForRecipient(outputLineSplit.at(1));
1648     if (!cipherKey.isEmpty())
1649     {
1650         int colon = outputLine.indexOf(QLatin1Char(':'));
1651         if (colon > -1)
1652         {
1653             colon++;
1654 
1655             QString pay(outputLine.mid(colon));
1656             //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements
1657             QByteArray payload = pay.toUtf8();
1658 
1659             QByteArray dest;
1660             if (codec)
1661             {
1662                 payload=codec->fromUnicode(pay);
1663                 //apparently channel name isn't a protocol element...
1664                 dest = codec->fromUnicode(outputLineSplit.at(1));
1665             }
1666             else
1667             {
1668                 dest = outputLineSplit.at(1).toLatin1();
1669             }
1670 
1671             if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3
1672             {
1673                 bool doit = true;
1674                 if (outboundCommand == 2)
1675                 {
1676                     //if its a privmsg and a ctcp but not an action, don't encrypt
1677                     //not interpreting `payload` in case encoding bollixed it
1678                     if (outputLineSplit.at(2).startsWith(QLatin1String(":\x01"))
1679                         && outputLineSplit.at(2) != QLatin1String(":\x01""ACTION"))
1680                         doit = false;
1681                 }
1682                 if (doit)
1683                 {
1684                     QString target = outputLineSplit.at(1);
1685 
1686                     if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey))
1687                         getChannelByName(target)->getCipher()->encrypt(payload);
1688                     else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey))
1689                         getQueryByName(target)->getCipher()->encrypt(payload);
1690 
1691                     encoded = outputLineSplit.at(0).toLatin1();
1692                     qCDebug(KONVERSATION_LOG) << payload << "\n" << payload.data();
1693                     //two lines because the compiler insists on using the wrong operator+
1694                     encoded += ' ' + dest + " :" + payload;
1695                 }
1696             }
1697         }
1698     }
1699     #endif
1700 
1701     if (m_rawLog)
1702         m_rawLog->appendRaw(RawLog::Outbound, encoded);
1703 
1704     encoded += '\n';
1705     qint64 sout = m_socket->write(encoded);
1706 
1707     return sout;
1708 }
1709 
toServer(const QString & s,IRCQueue * q)1710 void Server::toServer(const QString& s, IRCQueue* q)
1711 {
1712 
1713     int sizesent = _send_internal(s);
1714     Q_EMIT sentStat(s.length(), sizesent, q); //tell the queues what we sent
1715     collectStats(s.length(), sizesent);
1716 }
1717 
collectStats(int bytes,int encodedBytes)1718 void Server::collectStats(int bytes, int encodedBytes)
1719 {
1720     m_bytesSent += bytes;
1721     m_encodedBytesSent += encodedBytes;
1722     m_linesSent++;
1723 }
1724 
validQueue(QueuePriority priority)1725 bool Server::validQueue(QueuePriority priority)
1726 {
1727    if (priority >=0 && priority <= Application::instance()->countOfQueues())
1728        return true;
1729    return false;
1730 }
1731 
queue(const QString & line,QueuePriority priority)1732 bool Server::queue(const QString& line, QueuePriority priority)
1733 {
1734     if (!line.isEmpty() && validQueue(priority))
1735     {
1736         IRCQueue& out=*m_queues[priority];
1737         out.enqueue(line);
1738         return true;
1739     }
1740     return false;
1741 }
1742 
queueList(const QStringList & buffer,QueuePriority priority)1743 bool Server::queueList(const QStringList& buffer, QueuePriority priority)
1744 {
1745     if (buffer.isEmpty() || !validQueue(priority))
1746         return false;
1747 
1748     IRCQueue& out=*(m_queues[priority]);
1749 
1750     for (const QString& line : buffer) {
1751         if (!line.isEmpty())
1752             out.enqueue(line);
1753     }
1754     return true;
1755 }
1756 
resetQueues()1757 void Server::resetQueues()
1758 {
1759     m_incomingTimer.stop();
1760     m_inputBuffer.clear();
1761     for (int i=0; i <= Application::instance()->countOfQueues(); i++)
1762         m_queues[i]->reset();
1763 }
1764 
1765 //this could flood you off, but you're leaving anyway...
flushQueues()1766 void Server::flushQueues()
1767 {
1768     int cue;
1769     do
1770     {
1771         cue=-1;
1772         int wait=0;
1773         for (int i=1; i <= Application::instance()->countOfQueues(); i++) //slow queue can rot
1774         {
1775             IRCQueue *queue=m_queues[i];
1776             //higher queue indices have higher priorty, higher queue priority wins tie
1777             if (!queue->isEmpty() && queue->currentWait()>=wait)
1778             {
1779                 cue=i;
1780                 wait=queue->currentWait();
1781             }
1782         }
1783         if (cue>-1)
1784             m_queues[cue]->sendNow();
1785     } while (cue>-1);
1786 }
1787 
closed()1788 void Server::closed()
1789 {
1790     broken(m_socket->error());
1791 }
1792 
dbusRaw(const QString & command)1793 void Server::dbusRaw(const QString& command)
1794 {
1795     if(command.startsWith(Preferences::self()->commandChar()))
1796     {
1797         queue(command.section(Preferences::self()->commandChar(), 1));
1798     }
1799     else
1800         queue(command);
1801 }
1802 
dbusSay(const QString & target,const QString & command)1803 void Server::dbusSay(const QString& target,const QString& command)
1804 {
1805     if(isAChannel(target))
1806     {
1807         Channel* channel=getChannelByName(target);
1808         if(channel) channel->sendText(command);
1809     }
1810     else
1811     {
1812         Query* query = getQueryByName(target);
1813         if(query==nullptr)
1814         {
1815             NickInfoPtr nickinfo = obtainNickInfo(target);
1816             query=addQuery(nickinfo, true);
1817         }
1818         if(query)
1819         {
1820             if(!command.isEmpty())
1821                 query->sendText(command);
1822             else
1823             {
1824                 query->adjustFocus();
1825                 getViewContainer()->getWindow()->activateAndRaiseWindow();
1826             }
1827         }
1828     }
1829 }
1830 
dbusInfo(const QString & string)1831 void Server::dbusInfo(const QString& string)
1832 {
1833     appendMessageToFrontmost(i18n("D-Bus"),string);
1834 }
1835 
ctcpReply(const QString & receiver,const QString & text)1836 void Server::ctcpReply(const QString &receiver,const QString &text)
1837 {
1838     queue(QStringLiteral("NOTICE ")+receiver+QStringLiteral(" :")+QLatin1Char('\x01')+text+QLatin1Char('\x01'));
1839 }
1840 
1841 // Given a nickname, returns NickInfo object.   0 if not found.
getNickInfo(const QString & nickname) const1842 NickInfoPtr Server::getNickInfo(const QString& nickname) const
1843 {
1844     QString lcNickname(nickname.toLower());
1845     if (m_allNicks.contains(lcNickname))
1846     {
1847         NickInfoPtr nickinfo = m_allNicks[lcNickname];
1848         Q_ASSERT(nickinfo);
1849         return nickinfo;
1850     }
1851     else
1852         return NickInfoPtr(); //! TODO FIXME null null null
1853 }
1854 
1855 // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object.
1856 // Returns pointer to the found or created NickInfo object.
obtainNickInfo(const QString & nickname)1857 NickInfoPtr Server::obtainNickInfo(const QString& nickname)
1858 {
1859     NickInfoPtr nickInfo = getNickInfo(nickname);
1860     if (!nickInfo)
1861     {
1862         nickInfo = new NickInfo(nickname, this);
1863         m_allNicks.insert(QString(nickname.toLower()), nickInfo);
1864     }
1865     return nickInfo;
1866 }
1867 
getAllNicks() const1868 const NickInfoMap* Server::getAllNicks() const { return &m_allNicks; }
1869 
1870 // Returns the list of members for a channel in the joinedChannels list.
1871 // 0 if channel is not in the joinedChannels list.
1872 // Using code must not alter the list.
getJoinedChannelMembers(const QString & channelName) const1873 const ChannelNickMap *Server::getJoinedChannelMembers(const QString& channelName) const
1874 {
1875     QString lcChannelName = channelName.toLower();
1876     if (m_joinedChannels.contains(lcChannelName))
1877         return m_joinedChannels[lcChannelName];
1878     else
1879         return nullptr;
1880 }
1881 
1882 // Returns the list of members for a channel in the unjoinedChannels list.
1883 // 0 if channel is not in the unjoinedChannels list.
1884 // Using code must not alter the list.
getUnjoinedChannelMembers(const QString & channelName) const1885 const ChannelNickMap *Server::getUnjoinedChannelMembers(const QString& channelName) const
1886 {
1887     QString lcChannelName = channelName.toLower();
1888     if (m_unjoinedChannels.contains(lcChannelName))
1889         return m_unjoinedChannels[lcChannelName];
1890     else
1891         return nullptr;
1892 }
1893 
1894 // Searches the Joined and Unjoined lists for the given channel and returns the member list.
1895 // 0 if channel is not in either list.
1896 // Using code must not alter the list.
getChannelMembers(const QString & channelName) const1897 const ChannelNickMap *Server::getChannelMembers(const QString& channelName) const
1898 {
1899     const ChannelNickMap *members = getJoinedChannelMembers(channelName);
1900     if (members)
1901         return members;
1902     else
1903         return getUnjoinedChannelMembers(channelName);
1904 }
1905 
1906 // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname.
1907 // 0 if not found.
getChannelNick(const QString & channelName,const QString & nickname) const1908 ChannelNickPtr Server::getChannelNick(const QString& channelName, const QString& nickname) const
1909 {
1910     QString lcNickname = nickname.toLower();
1911     const ChannelNickMap *channelNickMap = getChannelMembers(channelName);
1912     if (channelNickMap)
1913     {
1914         if (channelNickMap->contains(lcNickname))
1915             return (*channelNickMap)[lcNickname];
1916         else
1917             return ChannelNickPtr(); //! TODO FIXME null null null
1918     }
1919     else
1920     {
1921         return ChannelNickPtr(); //! TODO FIXME null null null
1922     }
1923 }
1924 
1925 // Updates a nickname in a channel.  If not on the joined or unjoined lists, and nick
1926 // is in the watch list, adds the channel and nick to the unjoinedChannels list.
1927 // If mode != 99, sets the mode for the nick in the channel.
1928 // Returns the NickInfo object if nick is on any lists, otherwise 0.
setChannelNick(const QString & channelName,const QString & nickname,unsigned int mode)1929 ChannelNickPtr Server::setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode)
1930 {
1931     QString lcNickname = nickname.toLower();
1932     // If already on a list, update mode.
1933     ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname);
1934     if (!channelNick)
1935     {
1936         // If the nick is on the watch list, add channel and nick to unjoinedChannels list.
1937         if (getWatchList().contains(lcNickname, Qt::CaseInsensitive))
1938         {
1939             channelNick = addNickToUnjoinedChannelsList(channelName, nickname);
1940             channelNick->setMode(mode);
1941         }
1942         else return ChannelNickPtr(); //! TODO FIXME null null null
1943     }
1944 
1945     if (mode != 99) channelNick->setMode(mode);
1946     return channelNick;
1947 }
1948 
1949 // Returns a list of all the joined channels that a nick is in.
getNickJoinedChannels(const QString & nickname) const1950 QStringList Server::getNickJoinedChannels(const QString& nickname) const
1951 {
1952     QString lcNickname = nickname.toLower();
1953     QStringList channellist;
1954     ChannelMembershipMap::ConstIterator channel;
1955     for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel )
1956     {
1957         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1958     }
1959     return channellist;
1960 }
1961 
1962 // Returns a list of all the channels (joined or unjoined) that a nick is in.
getNickChannels(const QString & nickname) const1963 QStringList Server::getNickChannels(const QString& nickname) const
1964 {
1965     QString lcNickname = nickname.toLower();
1966     QStringList channellist;
1967     ChannelMembershipMap::ConstIterator channel;
1968     for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel )
1969     {
1970         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1971     }
1972     for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel )
1973     {
1974         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1975     }
1976     return channellist;
1977 }
1978 
getSharedChannels(const QString & nickname) const1979 QStringList Server::getSharedChannels(const QString& nickname) const
1980 {
1981     QString lcNickname = nickname.toLower();
1982     QStringList channellist;
1983     ChannelMembershipMap::ConstIterator channel;
1984     for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel )
1985     {
1986         if (channel.value()->contains(lcNickname)) channellist.append(channel.key());
1987     }
1988     return channellist;
1989 }
1990 
isNickOnline(const QString & nickname) const1991 bool Server::isNickOnline(const QString &nickname) const
1992 {
1993     NickInfoPtr nickInfo = getNickInfo(nickname);
1994     return (nickInfo != nullptr);
1995 }
1996 
getOwnIpByNetworkInterface() const1997 QString Server::getOwnIpByNetworkInterface() const
1998 {
1999     return m_socket->localAddress().toString();
2000 }
2001 
getOwnIpByServerMessage() const2002 QString Server::getOwnIpByServerMessage() const
2003 {
2004     if(!m_ownIpByWelcome.isEmpty())
2005         return m_ownIpByWelcome;
2006     else if(!m_ownIpByUserhost.isEmpty())
2007         return m_ownIpByUserhost;
2008     else
2009         return QString();
2010 }
2011 
addQuery(const NickInfoPtr & nickInfo,bool weinitiated)2012 Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated)
2013 {
2014     QString nickname = nickInfo->getNickname();
2015     // Only create new query object if there isn't already one with the same name
2016     Query* query = getQueryByName(nickname);
2017 
2018     if (!query)
2019     {
2020         QString lcNickname = nickname.toLower();
2021         query = getViewContainer()->addQuery(this, nickInfo, weinitiated);
2022 
2023         query->indicateAway(m_away);
2024 
2025         connect(query, &Query::sendFile, this, QOverload<>::of(&Server::requestDccSend));
2026         connect(this, &Server::serverOnline, query, &Query::serverOnline);
2027 
2028         // Append query to internal list
2029         m_queryList.append(query);
2030 
2031         m_queryNicks.insert(lcNickname, nickInfo);
2032 
2033         if (!weinitiated)
2034             Application::instance()->notificationHandler()->query(query, nickname);
2035     }
2036     else if (weinitiated)
2037     {
2038         Q_EMIT showView(query);
2039     }
2040 
2041     // try to get hostmask if there's none yet
2042     if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname);
2043 
2044     Q_ASSERT(query);
2045 
2046     return query;
2047 }
2048 
closeQuery(const QString & name)2049 void Server::closeQuery(const QString &name)
2050 {
2051     Query* query = getQueryByName(name);
2052     removeQuery(query);
2053 
2054     // Update NickInfo.  If no longer on any lists, delete it altogether, but
2055     // only if not on the watch list.  ISON replies will determine whether the NickInfo
2056     // is deleted altogether in that case.
2057     QString lcNickname = name.toLower();
2058     m_queryNicks.remove(lcNickname);
2059     if (!isWatchedNick(name)) deleteNickIfUnlisted(name);
2060 }
2061 
closeChannel(const QString & name)2062 void Server::closeChannel(const QString& name)
2063 {
2064     qCDebug(KONVERSATION_LOG) << "Server::closeChannel(" << name << ")";
2065     Channel* channelToClose = getChannelByName(name);
2066 
2067     if (channelToClose && channelToClose->isJoined()) {
2068         Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),
2069             Preferences::self()->commandChar() + QStringLiteral("PART"), name);
2070         queue(result.toServer);
2071     }
2072 }
2073 
requestChannelList()2074 void Server::requestChannelList()
2075 {
2076     m_inputFilter.setAutomaticRequest(QStringLiteral("LIST"), QString(), true);
2077     queue(QStringLiteral("LIST"));
2078 }
2079 
requestWhois(const QString & nickname)2080 void Server::requestWhois(const QString& nickname)
2081 {
2082     m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true);
2083     queue(QStringLiteral("WHOIS ")+nickname, LowPriority);
2084 }
2085 
requestWho(const QString & channel)2086 void Server::requestWho(const QString& channel)
2087 {
2088     if(m_whoRequestsDisabled)
2089         return;
2090 
2091     m_inputFilter.setAutomaticRequest(QStringLiteral("WHO"), channel, true);
2092     QString command(QStringLiteral("WHO ") + channel);
2093 
2094     if (capabilities() & WHOX && capabilities() & ExtendedJoin)
2095     {
2096         // Request the account as well as the usual info.
2097         // See http://faerion.sourceforge.net/doc/irc/whox.var
2098         // for more info.
2099         command += QStringLiteral(" nuhsa%cuhsnfdra");
2100     }
2101 
2102     queue(command, LowPriority);
2103 }
2104 
requestUserhost(const QString & nicks)2105 void Server::requestUserhost(const QString& nicks)
2106 {
2107     if(m_whoRequestsDisabled)
2108         return;
2109 
2110 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
2111     const QStringList nicksList = nicks.split(QLatin1Char(' '), QString::SkipEmptyParts);
2112 #else
2113     const QStringList nicksList = nicks.split(QLatin1Char(' '), Qt::SkipEmptyParts);
2114 #endif
2115     for (const QString& nick : nicksList)
2116         m_inputFilter.setAutomaticRequest(QStringLiteral("USERHOST"), nick, true);
2117     queue(QStringLiteral("USERHOST ")+nicks, LowPriority);
2118 }
2119 
requestTopic(const QString & channel)2120 void Server::requestTopic(const QString& channel)
2121 {
2122     m_inputFilter.setAutomaticRequest(QStringLiteral("TOPIC"), channel, true);
2123     queue(QStringLiteral("TOPIC ")+channel, LowPriority);
2124 }
2125 
resolveUserhost(const QString & nickname)2126 void Server::resolveUserhost(const QString& nickname)
2127 {
2128     m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true);
2129     m_inputFilter.setAutomaticRequest(QStringLiteral("DNS"), nickname, true);
2130     queue(QStringLiteral("WHOIS ")+nickname, LowPriority); //FIXME when is this really used?
2131 }
2132 
requestBan(const QStringList & users,const QString & channel,const QString & a_option)2133 void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option)
2134 {
2135     QString option=a_option.toLower();
2136 
2137     Channel* targetChannel=getChannelByName(channel);
2138 
2139     for (QString mask : users) {
2140         // first, set the ban mask to the specified nick
2141         // did we specify an option?
2142         if(!option.isEmpty())
2143         {
2144             // try to find specified nick on the channel
2145             Nick* targetNick=targetChannel->getNickByName(mask);
2146             // if we found the nick try to find their hostmask
2147             if(targetNick)
2148             {
2149                 QString hostmask=targetNick->getChannelNick()->getHostmask();
2150                 // if we found the hostmask, add it to the ban mask
2151                 if(!hostmask.isEmpty())
2152                 {
2153                     mask=targetNick->getChannelNick()->getNickname()+QLatin1Char('!')+hostmask;
2154 
2155                     // adapt ban mask to the option given
2156                     if(option==QStringLiteral("host"))
2157                         mask=QLatin1String("*!*@*.")+hostmask.section(QLatin1Char('.'),1);
2158                     else if(option==QStringLiteral("domain"))
2159                         mask=QLatin1String("*!*@")+hostmask.section(QLatin1Char('@'),1);
2160                     else if(option==QStringLiteral("userhost"))
2161                         mask=QLatin1String("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1String("@*.")+hostmask.section(QLatin1Char('.'),1);
2162                     else if(option==QStringLiteral("userdomain"))
2163                         mask=QLatin1String("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1Char('@')+hostmask.section(QLatin1Char('@'),1);
2164                 }
2165             }
2166         }
2167 
2168         Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel);
2169         queue(result.toServer);
2170     }
2171 }
2172 
requestUnban(const QString & mask,const QString & channel)2173 void Server::requestUnban(const QString& mask,const QString& channel)
2174 {
2175     Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel);
2176     queue(result.toServer);
2177 }
2178 
requestDccSend()2179 void Server::requestDccSend()
2180 {
2181     requestDccSend(QString());
2182 }
2183 
sendURIs(const QList<QUrl> & uris,const QString & nick)2184 void Server::sendURIs(const QList<QUrl>& uris, const QString& nick)
2185 {
2186     for (const QUrl &uri : uris)
2187          addDccSend(nick, uri);
2188 }
2189 
requestDccSend(const QString & a_recipient)2190 void Server::requestDccSend(const QString &a_recipient)
2191 {
2192     QString recipient(a_recipient);
2193     // if we don't have a recipient yet, let the user select one
2194     if (recipient.isEmpty())
2195     {
2196         recipient = recipientNick();
2197     }
2198 
2199     // do we have a recipient *now*?
2200     if(!recipient.isEmpty())
2201     {
2202         QPointer<DccFileDialog> dlg = new DccFileDialog (getViewContainer()->getWindow());
2203         //DccFileDialog fileDialog(KUrl(), QString(), getViewContainer()->getWindow());
2204         const QList<QUrl> fileURLs = dlg->getOpenUrls(
2205             QUrl(),
2206             QString(),
2207             i18n("Select File(s) to Send to %1", recipient)
2208         );
2209         for (const QUrl& fileUrl : fileURLs) {
2210             addDccSend(recipient, fileUrl, dlg->passiveSend());
2211         }
2212         delete dlg;
2213     }
2214 }
2215 
slotNewDccTransferItemQueued(DCC::Transfer * transfer)2216 void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer)
2217 {
2218     if (transfer->getConnectionId() == connectionId() )
2219     {
2220         qCDebug(KONVERSATION_LOG) << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]";
2221         if ( transfer->getType() == DCC::Transfer::Receive )
2222         {
2223             connect( transfer, &DCC::Transfer::done, this, &Server::dccGetDone );
2224             connect( transfer, &DCC::Transfer::statusChanged, this, &Server::dccStatusChanged );
2225         }
2226         else
2227         {
2228             connect( transfer, &DCC::Transfer::done, this, &Server::dccSendDone );
2229             connect( transfer, &DCC::Transfer::statusChanged, this, &Server::dccStatusChanged );
2230         }
2231     }
2232 }
2233 
addDccSend(const QString & recipient,const QUrl & fileURL,bool passive,const QString & altFileName,quint64 fileSize)2234 void Server::addDccSend(const QString &recipient, const QUrl &fileURL, bool passive, const QString &altFileName, quint64 fileSize)
2235 {
2236     if (!fileURL.isValid())
2237     {
2238         return;
2239     }
2240 
2241     // We already checked that the file exists in output filter / requestDccSend() resp.
2242     DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload();
2243 
2244     newDcc->setConnectionId(connectionId());
2245 
2246     newDcc->setPartnerNick(recipient);
2247     newDcc->setFileURL(fileURL);
2248     newDcc->setReverse(passive);
2249     if (!altFileName.isEmpty())
2250         newDcc->setFileName(altFileName);
2251     if (fileSize != 0)
2252         newDcc->setFileSize(fileSize);
2253 
2254     Q_EMIT addDccPanel();
2255 
2256     if (newDcc->queue())
2257         newDcc->start();
2258 }
2259 
recoverDccFileName(const QStringList & dccArguments,int offset) const2260 QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const
2261 {
2262     QString fileName;
2263     if(dccArguments.count() > offset + 1)
2264     {
2265         qCDebug(KONVERSATION_LOG) << "recover filename";
2266         const int argumentOffsetSize = dccArguments.size() - offset;
2267         for (int i = 0; i < argumentOffsetSize; ++i)
2268         {
2269             fileName += dccArguments.at(i);
2270             //if not last element, append a space
2271             if (i < (argumentOffsetSize - 1))
2272             {
2273                 fileName += QLatin1Char(' ');
2274             }
2275         }
2276     }
2277     else
2278     {
2279         fileName = dccArguments.at(0);
2280     }
2281 
2282     return cleanDccFileName(fileName);
2283 }
2284 
cleanDccFileName(const QString & filename) const2285 QString Server::cleanDccFileName(const QString& filename) const
2286 {
2287     QString cleanFileName = filename;
2288 
2289     //we want a clean filename to get rid of the mass """filename"""
2290     //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars)
2291     //      but most clients don't support that and just replace it with a _
2292     while (cleanFileName.startsWith(QLatin1Char('\"')) && cleanFileName.endsWith(QLatin1Char('\"')))
2293     {
2294         cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2);
2295     }
2296 
2297     return cleanFileName;
2298 }
2299 
stringToPort(const QString & port,bool * ok)2300 quint16 Server::stringToPort(const QString &port, bool *ok)
2301 {
2302     bool toUintOk = false;
2303     uint uPort32 = port.toUInt(&toUintOk);
2304     // ports over 65535 are invalid, someone sends us bad data
2305     if (!toUintOk || uPort32 > USHRT_MAX)
2306     {
2307         if (ok)
2308         {
2309             *ok = false;
2310         }
2311     }
2312     else
2313     {
2314         if (ok)
2315         {
2316             *ok = true;
2317         }
2318     }
2319     return (quint16)uPort32;
2320 }
2321 
recipientNick() const2322 QString Server::recipientNick() const
2323 {
2324     QStringList nickList;
2325 
2326     // fill nickList with all nicks we know about
2327     for (Channel* lookChannel : m_channelList) {
2328         const auto lookChannelNickList = lookChannel->getNickList();
2329         for (Nick* lookNick : lookChannelNickList) {
2330             if (!nickList.contains(lookNick->getChannelNick()->getNickname()))
2331                 nickList.append(lookNick->getChannelNick()->getNickname());
2332         }
2333     }
2334 
2335     // add Queries as well, but don't insert duplicates
2336     for (Query* lookQuery : m_queryList) {
2337         if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName());
2338     }
2339     QStringListModel model;
2340     model.setStringList(nickList);
2341     return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model);
2342 }
2343 
addDccGet(const QString & sourceNick,const QStringList & dccArguments)2344 void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments)
2345 {
2346     //filename ip port filesize [token]
2347     QString ip;
2348     quint16 port;
2349     QString fileName;
2350     quint64 fileSize;
2351     QString token;
2352     const int argumentSize = dccArguments.count();
2353     bool ok = true;
2354 
2355     if (dccArguments.at(argumentSize - 3) == QLatin1Char('0')) //port==0, for passive send, ip can't be 0
2356     {
2357         //filename ip port(0) filesize token
2358         fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token
2359         ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize
2360         port = 0;
2361         fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token
2362         token = dccArguments.at(argumentSize - 1); //-1 index
2363     }
2364     else
2365     {
2366         //filename ip port filesize
2367         ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize
2368         fileName = recoverDccFileName(dccArguments, 3); //ip port filesize
2369         fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index
2370         port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize
2371     }
2372 
2373     if (!ok)
2374     {
2375         appendMessageToFrontmost(i18n("Error"),
2376                                  i18nc("%1=nickname","Received invalid DCC SEND request from %1.",
2377                                        sourceNick));
2378         return;
2379     }
2380 
2381     DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload();
2382 
2383     newDcc->setConnectionId(connectionId());
2384     newDcc->setPartnerNick(sourceNick);
2385 
2386     newDcc->setPartnerIp(ip);
2387     newDcc->setPartnerPort(port);
2388     newDcc->setFileName(fileName);
2389     newDcc->setFileSize(fileSize);
2390     // Reverse DCC
2391     if (!token.isEmpty())
2392     {
2393         newDcc->setReverse(true, token);
2394     }
2395 
2396     qCDebug(KONVERSATION_LOG) << "ip: " << ip;
2397     qCDebug(KONVERSATION_LOG) << "port: " << port;
2398     qCDebug(KONVERSATION_LOG) << "filename: " << fileName;
2399     qCDebug(KONVERSATION_LOG) << "filesize: " << fileSize;
2400     qCDebug(KONVERSATION_LOG) << "token: " << token;
2401 
2402     //emit after data was set
2403     Q_EMIT addDccPanel();
2404 
2405     if ( newDcc->queue() )
2406     {
2407         appendMessageToFrontmost( i18n( "DCC" ),
2408                                   i18n( "%1 offers to send you \"%2\" (%3)...",
2409                                         newDcc->getPartnerNick(),
2410                                         fileName,
2411                                         ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) );
2412 
2413         if (Preferences::self()->dccAutoGet())
2414             newDcc->start();
2415     }
2416 }
2417 
addDccChat(const QString & sourceNick,const QStringList & dccArguments)2418 void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments)
2419 {
2420     //chat ip port [token]
2421     QString ip;
2422     quint16 port = 0;
2423     QString token;
2424     bool reverse = false;
2425     const int argumentSize = dccArguments.count();
2426     bool ok = true;
2427     QString extension;
2428 
2429     extension = dccArguments.at(0);
2430     ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1));
2431 
2432     if (argumentSize == 3)
2433     {
2434         //extension ip port
2435         port = stringToPort(dccArguments.at(2), &ok);
2436     }
2437     else if (argumentSize == 4)
2438     {
2439         //extension ip port(0) token
2440         token = dccArguments.at(3);
2441         reverse = true;
2442     }
2443 
2444     if (!ok)
2445     {
2446         appendMessageToFrontmost(i18n("Error"),
2447                                  i18nc("%1=nickname","Received invalid DCC CHAT request from %1.",
2448                                        sourceNick));
2449         return;
2450     }
2451 
2452     DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2453 
2454     newChat->setConnectionId(connectionId());
2455     newChat->setPartnerNick(sourceNick);
2456     newChat->setOwnNick(getNickname());
2457 
2458     qCDebug(KONVERSATION_LOG) << "ip: " << ip;
2459     qCDebug(KONVERSATION_LOG) << "port: " << port;
2460     qCDebug(KONVERSATION_LOG) << "token: " << token;
2461     qCDebug(KONVERSATION_LOG) << "extension: " << extension;
2462 
2463     newChat->setPartnerIp(ip);
2464     newChat->setPartnerPort(port);
2465     newChat->setReverse(reverse, token);
2466     newChat->setSelfOpened(false);
2467     newChat->setExtension(extension);
2468 
2469     Q_EMIT addDccChat(newChat);
2470     newChat->start();
2471 }
2472 
openDccChat(const QString & nickname)2473 void Server::openDccChat(const QString& nickname)
2474 {
2475     qCDebug(KONVERSATION_LOG) << __FUNCTION__;
2476     QString recipient(nickname);
2477     // if we don't have a recipient yet, let the user select one
2478     if (recipient.isEmpty())
2479     {
2480         recipient = recipientNick();
2481     }
2482 
2483     // do we have a recipient *now*?
2484     if (!recipient.isEmpty())
2485     {
2486         DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2487         newChat->setConnectionId(connectionId());
2488         newChat->setPartnerNick(recipient);
2489         newChat->setOwnNick(getNickname());
2490         newChat->setSelfOpened(true);
2491         Q_EMIT addDccChat(newChat);
2492         newChat->start();
2493     }
2494 }
2495 
openDccWBoard(const QString & nickname)2496 void Server::openDccWBoard(const QString& nickname)
2497 {
2498     qCDebug(KONVERSATION_LOG) << __FUNCTION__;
2499     QString recipient(nickname);
2500     // if we don't have a recipient yet, let the user select one
2501     if (recipient.isEmpty())
2502     {
2503         recipient = recipientNick();
2504     }
2505 
2506     // do we have a recipient *now*?
2507     if (!recipient.isEmpty())
2508     {
2509         DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat();
2510         newChat->setConnectionId(connectionId());
2511         newChat->setPartnerNick(recipient);
2512         newChat->setOwnNick(getNickname());
2513         // Set extension before emiting addDccChat
2514         newChat->setExtension(DCC::Chat::Whiteboard);
2515         newChat->setSelfOpened(true);
2516         Q_EMIT addDccChat(newChat);
2517         newChat->start();
2518     }
2519 }
2520 
requestDccChat(const QString & partnerNick,const QString & extension,const QString & numericalOwnIp,quint16 ownPort)2521 void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort)
2522 {
2523     Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort);
2524     queue(result.toServer);
2525 }
2526 
acceptDccGet(const QString & nick,const QString & file)2527 void Server::acceptDccGet(const QString& nick, const QString& file)
2528 {
2529     Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file);
2530 }
2531 
dccSendRequest(const QString & partner,const QString & fileName,const QString & address,quint16 port,quint64 size)2532 void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size)
2533 {
2534     Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size);
2535     queue(result.toServer);
2536 
2537     appendMessageToFrontmost( i18n( "DCC" ),
2538                               i18n( "Asking %1 to accept upload of \"%2\" (%3)...",
2539                                     partner,
2540                                     cleanDccFileName(fileName),
2541                                     ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) );
2542 }
2543 
dccPassiveSendRequest(const QString & recipient,const QString & fileName,const QString & address,quint64 size,const QString & token)2544 void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token)
2545 {
2546     Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token);
2547     queue(result.toServer);
2548 
2549     appendMessageToFrontmost( i18n( "DCC" ),
2550                               i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...",
2551                                     recipient,
2552                                     cleanDccFileName(fileName),
2553                                     ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) );
2554 }
2555 
dccPassiveChatRequest(const QString & recipient,const QString & extension,const QString & address,const QString & token)2556 void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token)
2557 {
2558     Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token);
2559     queue(result.toServer);
2560 
2561     appendMessageToFrontmost(i18n("DCC"),
2562                              i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension));
2563 }
2564 
dccPassiveResumeGetRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt,const QString & token)2565 void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token)
2566 {
2567     Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);;
2568     queue(result.toServer);
2569 }
2570 
dccResumeGetRequest(const QString & sender,const QString & fileName,quint16 port,KIO::filesize_t startAt)2571 void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt)
2572 {
2573     Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);;
2574     queue(result.toServer);
2575 }
2576 
dccReverseSendAck(const QString & partnerNick,const QString & fileName,const QString & ownAddress,quint16 ownPort,quint64 size,const QString & reverseToken)2577 void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken)
2578 {
2579     Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken);
2580     queue(result.toServer);
2581 }
2582 
dccReverseChatAck(const QString & partnerNick,const QString & extension,const QString & ownAddress,quint16 ownPort,const QString & reverseToken)2583 void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken)
2584 {
2585     Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken);
2586     queue(result.toServer);
2587 }
2588 
dccRejectSend(const QString & partnerNick,const QString & fileName)2589 void Server::dccRejectSend(const QString& partnerNick, const QString& fileName)
2590 {
2591     Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName);
2592     queue(result.toServer);
2593 }
2594 
dccRejectChat(const QString & partnerNick,const QString & extension)2595 void Server::dccRejectChat(const QString& partnerNick, const QString& extension)
2596 {
2597     Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension);
2598     queue(result.toServer);
2599 }
2600 
startReverseDccChat(const QString & sourceNick,const QStringList & dccArguments)2601 void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments)
2602 {
2603     qCDebug(KONVERSATION_LOG) << __FUNCTION__;
2604     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2605 
2606     bool ok = true;
2607     QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1));
2608     quint16 port = stringToPort(dccArguments.at(2), &ok);
2609     QString token = dccArguments.at(3);
2610 
2611     qCDebug(KONVERSATION_LOG) << "ip: " << partnerIP;
2612     qCDebug(KONVERSATION_LOG) << "port: " << port;
2613     qCDebug(KONVERSATION_LOG) << "token: " << token;
2614 
2615     if (!ok || dtm->startReverseChat(connectionId(), sourceNick,
2616                                     partnerIP, port, token) == nullptr)
2617     {
2618         // DTM could not find a matched item
2619         appendMessageToFrontmost(i18n("Error"),
2620                                  i18nc("%1 = nickname",
2621                                        "Received invalid passive DCC chat acceptance message from %1.",
2622                                        sourceNick));
2623     }
2624 }
2625 
startReverseDccSendTransfer(const QString & sourceNick,const QStringList & dccArguments)2626 void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments)
2627 {
2628     qCDebug(KONVERSATION_LOG) << __FUNCTION__;
2629     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2630 
2631     bool ok = true;
2632     const int argumentSize = dccArguments.size();
2633     QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) );
2634     quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok);
2635     QString token = dccArguments.at(argumentSize - 1);
2636     quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong();
2637     QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token
2638 
2639     qCDebug(KONVERSATION_LOG) << "ip: " << partnerIP;
2640     qCDebug(KONVERSATION_LOG) << "port: " << port;
2641     qCDebug(KONVERSATION_LOG) << "filename: " << fileName;
2642     qCDebug(KONVERSATION_LOG) << "filesize: " << fileSize;
2643     qCDebug(KONVERSATION_LOG) << "token: " << token;
2644 
2645     if (!ok ||
2646         dtm->startReverseSending(connectionId(), sourceNick,
2647                                  fileName,  // filename
2648                                  partnerIP,  // partner IP
2649                                  port,  // partner port
2650                                  fileSize,  // filesize
2651                                  token  // Reverse DCC token
2652          ) == nullptr)
2653     {
2654         // DTM could not find a matched item
2655         appendMessageToFrontmost(i18n("Error"),
2656                                  i18nc("%1 = file name, %2 = nickname",
2657                                        "Received invalid passive DCC send acceptance message for \"%1\" from %2.",
2658                                        fileName,
2659                                        sourceNick));
2660     }
2661 }
2662 
resumeDccGetTransfer(const QString & sourceNick,const QStringList & dccArguments)2663 void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments)
2664 {
2665     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2666 
2667     //filename port position [token]
2668     QString fileName;
2669     quint64 position;
2670     quint16 ownPort;
2671     bool ok = true;
2672     const int argumentSize = dccArguments.count();
2673 
2674     if (dccArguments.at(argumentSize - 3) == QLatin1Char('0')) //-1 index, -1 token, -1 pos
2675     {
2676         fileName = recoverDccFileName(dccArguments, 3); //port position token
2677         ownPort = 0;
2678         position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token
2679     }
2680     else
2681     {
2682         fileName = recoverDccFileName(dccArguments, 2); //port position
2683         ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos
2684         position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index
2685     }
2686     //do we need the token here?
2687 
2688     DCC::TransferRecv* dccTransfer = nullptr;
2689     if (ok)
2690     {
2691         dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position);
2692     }
2693 
2694     if (dccTransfer)
2695     {
2696         appendMessageToFrontmost(i18n("DCC"),
2697                                  i18nc("%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size",
2698                                        "Resuming download of \"%1\" from %2 starting at %3% of %4...",
2699                                        fileName,
2700                                        sourceNick,
2701                                        QString::number( dccTransfer->getProgress()),
2702                                        (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize())));
2703     }
2704     else
2705     {
2706         appendMessageToFrontmost(i18n("Error"),
2707                                  i18nc("%1 = file name, %2 = nickname",
2708                                        "Received invalid resume acceptance message for \"%1\" from %2.",
2709                                        fileName,
2710                                        sourceNick));
2711     }
2712 }
2713 
resumeDccSendTransfer(const QString & sourceNick,const QStringList & dccArguments)2714 void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments)
2715 {
2716     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2717 
2718     bool passiv = false;
2719     QString fileName;
2720     quint64 position;
2721     QString token;
2722     quint16 ownPort;
2723     bool ok = true;
2724     const int argumentSize = dccArguments.count();
2725 
2726     //filename port filepos [token]
2727     if (dccArguments.at( argumentSize - 3) == QLatin1Char('0'))
2728     {
2729         //filename port filepos token
2730         passiv = true;
2731         ownPort = 0;
2732         token = dccArguments.at( argumentSize - 1); // -1 index
2733         position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token
2734         fileName = recoverDccFileName(dccArguments, 3); //port filepos token
2735     }
2736     else
2737     {
2738         //filename port filepos
2739         ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize
2740         position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index
2741         fileName = recoverDccFileName(dccArguments, 2); //port filepos
2742     }
2743 
2744     DCC::TransferSend* dccTransfer = nullptr;
2745     if (ok)
2746     {
2747         dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position);
2748     }
2749 
2750     if (dccTransfer)
2751     {
2752         appendMessageToFrontmost(i18n("DCC"),
2753                                  i18nc("%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size",
2754                                        "Resuming upload of \"%1\" to %2 starting at %3% of %4...",
2755                                        fileName,
2756                                        sourceNick,
2757                                        QString::number(dccTransfer->getProgress()),
2758                                        (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize())));
2759 
2760         // fileName can't have " here
2761         if (fileName.contains(QLatin1Char(' ')))
2762             fileName = QLatin1Char('\"')+fileName+QLatin1Char('\"');
2763 
2764         // FIXME: this operation should be done by TransferManager
2765         Konversation::OutputFilterResult result;
2766         if (passiv)
2767             result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token );
2768         else
2769             result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position );
2770         queue( result.toServer );
2771     }
2772     else
2773     {
2774         appendMessageToFrontmost(i18n("Error"),
2775                                  i18nc("%1 = file name, %2 = nickname",
2776                                        "Received invalid resume request for \"%1\" from %2.",
2777                                        fileName,
2778                                        sourceNick));
2779     }
2780 }
2781 
rejectDccSendTransfer(const QString & sourceNick,const QStringList & dccArguments)2782 void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments)
2783 {
2784     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2785 
2786     //filename
2787     QString fileName = recoverDccFileName(dccArguments, 0);
2788 
2789     DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName);
2790 
2791     if (!dccTransfer)
2792     {
2793         appendMessageToFrontmost(i18n("Error"),
2794                                  i18nc("%1 = file name, %2 = nickname",
2795                                        "Received invalid reject request for \"%1\" from %2.",
2796                                        fileName,
2797                                        sourceNick));
2798     }
2799 }
2800 
rejectDccChat(const QString & sourceNick)2801 void Server::rejectDccChat(const QString& sourceNick)
2802 {
2803     DCC::TransferManager* dtm = Application::instance()->getDccTransferManager();
2804 
2805     DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick);
2806 
2807     if (!dccChat)
2808     {
2809         appendMessageToFrontmost(i18n("Error"),
2810                                  i18nc("%1 = nickname",
2811                                        "Received invalid reject request from %1.",
2812                                        sourceNick));
2813     }
2814 }
2815 
dccGetDone(DCC::Transfer * item)2816 void Server::dccGetDone(DCC::Transfer* item)
2817 {
2818     if (!item)
2819         return;
2820 
2821     if(item->getStatus() == DCC::Transfer::Done)
2822     {
2823         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender",
2824             "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick()));
2825     }
2826     else if(item->getStatus() == DCC::Transfer::Failed)
2827     {
2828         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender",
2829             "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(),
2830             item->getPartnerNick(), item->getStatusDetail()));
2831     }
2832 }
2833 
dccSendDone(DCC::Transfer * item)2834 void Server::dccSendDone(DCC::Transfer* item)
2835 {
2836     if (!item)
2837         return;
2838 
2839     if(item->getStatus() == DCC::Transfer::Done)
2840         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient",
2841             "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick()));
2842     else if(item->getStatus() == DCC::Transfer::Failed)
2843         appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient",
2844             "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(),
2845             item->getStatusDetail()));
2846 }
2847 
dccStatusChanged(DCC::Transfer * item,int newStatus,int oldStatus)2848 void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus)
2849 {
2850     if(!item)
2851         return;
2852 
2853     if ( item->getType() == DCC::Transfer::Send )
2854     {
2855         // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message
2856         if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() )
2857             appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient",
2858                 "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) );
2859     }
2860     else  // type == Receive
2861     {
2862         if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() )
2863         {
2864             appendMessageToFrontmost( i18n( "DCC" ),
2865                                         i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...",
2866                                               item->getFileName(),
2867                                             ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ),
2868                                             item->getPartnerNick() ) );
2869         }
2870     }
2871 }
2872 
removeQuery(Query * query)2873 void Server::removeQuery(Query* query)
2874 {
2875     m_queryList.removeOne(query);
2876     query->deleteLater();
2877 }
2878 
sendJoinCommand(const QString & name,const QString & password)2879 void Server::sendJoinCommand(const QString& name, const QString& password)
2880 {
2881     Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),
2882         Preferences::self()->commandChar() + QStringLiteral("JOIN ") + name + QLatin1Char(' ') + password, QString());
2883     queue(result.toServer);
2884 }
2885 
joinChannel(const QString & name,const QString & hostmask,const QHash<QString,QString> & messageTags)2886 Channel* Server::joinChannel(const QString& name, const QString& hostmask, const QHash<QString, QString> &messageTags)
2887 {
2888     // (re-)join channel, open a new panel if needed
2889     Channel* channel = getChannelByName(name);
2890 
2891     if (!channel)
2892     {
2893         channel=getViewContainer()->addChannel(this,name);
2894         Q_ASSERT(channel);
2895         channel->setNickname(getNickname());
2896         channel->indicateAway(m_away);
2897 
2898         if (getServerGroup())
2899         {
2900             Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name);
2901             channel->setNotificationsEnabled(channelSettings.enableNotifications());
2902             getServerGroup()->appendChannelHistory(channelSettings);
2903         }
2904 
2905         m_channelList.append(channel);
2906         m_loweredChannelNameHash.insert(channel->getName().toLower(), channel);
2907 
2908         connect(channel, &Channel::sendFile, this, QOverload<>::of(&Server::requestDccSend));
2909         connect(this, &Server::nicknameChanged, channel, &Channel::setNickname);
2910     }
2911 
2912     // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list.
2913     ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname());
2914 
2915     if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty())
2916     {
2917         NickInfoPtr nickInfo = channelNick->getNickInfo();
2918         nickInfo->setHostmask(hostmask);
2919     }
2920 
2921     channel->joinNickname(channelNick, messageTags);
2922 
2923     return channel;
2924 }
2925 
removeChannel(Channel * channel)2926 void Server::removeChannel(Channel* channel)
2927 {
2928     // Update NickInfo.
2929     removeJoinedChannel(channel->getName());
2930 
2931     if (getServerGroup())
2932     {
2933         Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName());
2934         channelSettings.setNotificationsEnabled(channel->notificationsEnabled());
2935         getServerGroup()->appendChannelHistory(channelSettings);
2936     }
2937 
2938     m_channelList.removeOne(channel);
2939     m_loweredChannelNameHash.remove(channel->getName().toLower());
2940 
2941     if (!isConnected())
2942         updateAutoJoin();
2943 }
2944 
updateChannelMode(const QString & updater,const QString & channelName,char mode,bool plus,const QString & parameter,const QHash<QString,QString> & messageTags)2945 void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString &parameter, const QHash<QString, QString> &messageTags)
2946 {
2947 
2948     Channel* channel=getChannelByName(channelName);
2949 
2950     if(channel)                                   //Let the channel be verbose to the screen about the change, and update channelNick
2951         channel->updateMode(updater, mode, plus, parameter, messageTags);
2952     // TODO: What is mode character for owner?
2953     // Answer from JOHNFLUX - I think that admin is the same as owner.  Channel.h has owner as "a"
2954     // "q" is the likely answer.. UnrealIRCd and euIRCd use it.
2955     // TODO these need to become dynamic
2956     QString userModes=QStringLiteral("vhoqa");                    // voice halfop op owner admin
2957     int modePos = userModes.indexOf(QLatin1Char(mode));
2958     if (modePos > 0)
2959     {
2960         ChannelNickPtr updateeNick = getChannelNick(channelName, parameter);
2961         if(!updateeNick)
2962         {
2963 /*
2964             if(parameter.isEmpty())
2965             {
2966                 qCDebug(KONVERSATION_LOG) << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <<plus << ") in channel '" << channelName << "' by " << updater << ".  How this happened, I have no idea.  Please report this message to irc #konversation if you want to be helpful." << endl << "Ignoring the error and continuing.";
2967                                                   //this will get their attention.
2968                 qCDebug(KONVERSATION_LOG) << kBacktrace();
2969             }
2970             else
2971             {
2972                 qCDebug(KONVERSATION_LOG) << "in updateChannelMode, could not find updatee nick " << parameter << " for channel " << channelName;
2973                 qCDebug(KONVERSATION_LOG) << "This could indicate an obscure race condition that is safely being handled (like the mode of someone changed and they quit almost simulatanously, or it could indicate an internal error.";
2974             }
2975 */
2976             //TODO Do we need to add this nick?
2977             return;
2978         }
2979 
2980         updateeNick->setMode(mode, plus);
2981 
2982         // Note that channel will be moved to joined list if necessary.
2983         addNickToJoinedChannelsList(channelName, parameter);
2984     }
2985 
2986     // Update channel ban list.
2987     if (mode == 'b')
2988     {
2989         if (plus)
2990         {
2991             addBan(channelName, QStringLiteral("%1 %2 %3").arg(parameter, updater).arg(QDateTime::currentDateTime().toSecsSinceEpoch()));
2992         } else {
2993             removeBan(channelName, parameter);
2994         }
2995     }
2996 }
2997 
updateChannelModeWidgets(const QString & channelName,char mode,const QString & parameter)2998 void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString &parameter)
2999 {
3000     Channel* channel=getChannelByName(channelName);
3001     if(channel) channel->updateModeWidgets(mode,true,parameter);
3002 }
3003 
getChannelByName(const QString & name) const3004 Channel* Server::getChannelByName(const QString& name) const
3005 {
3006     if (name.isEmpty()) {
3007         return nullptr;
3008     }
3009 
3010     // Convert wanted channel name to lowercase
3011     QString wanted = name.toLower();
3012 
3013     QRegularExpressionMatch p = m_targetMatcher.match(wanted);
3014     int index = p.capturedStart(2);
3015 
3016     if (index >= 0)
3017     {
3018         wanted = wanted.mid(index);
3019 
3020         if (m_loweredChannelNameHash.contains(wanted))
3021             return m_loweredChannelNameHash.value(wanted);
3022     }
3023 
3024     return nullptr;
3025 }
3026 
getQueryByName(const QString & name) const3027 Query* Server::getQueryByName(const QString& name) const
3028 {
3029     // Convert wanted query name to lowercase
3030     QString wanted = name.toLower();
3031 
3032     // Traverse through list to find the query with "name"
3033     for (Query* lookQuery : qAsConst(m_queryList)) {
3034         if(lookQuery->getName().toLower()==wanted) return lookQuery;
3035     }
3036     // No query by that name found? Must be a new query request. Return 0
3037     return nullptr;
3038 }
3039 
getChannelOrQueryByName(const QString & name) const3040 ChatWindow* Server::getChannelOrQueryByName(const QString& name) const
3041 {
3042     ChatWindow* window = getChannelByName(name);
3043 
3044     if (!window)
3045         window = getQueryByName(name);
3046 
3047     return window;
3048 }
3049 
queueNicks(const QString & channelName,const QStringList & nicknameList)3050 void Server::queueNicks(const QString& channelName, const QStringList& nicknameList)
3051 {
3052     Channel* channel = getChannelByName(channelName);
3053     if (channel) channel->queueNicks(nicknameList);
3054 }
3055 
3056 // Adds a nickname to the joinedChannels list.
3057 // Creates new NickInfo if necessary.
3058 // If needed, moves the channel from the unjoined list to the joined list.
3059 // Returns the NickInfo for the nickname.
addNickToJoinedChannelsList(const QString & channelName,const QString & nickname)3060 ChannelNickPtr Server::addNickToJoinedChannelsList(const QString& channelName, const QString& nickname)
3061 {
3062     bool doChannelJoinedSignal = false;
3063     bool doWatchedNickChangedSignal = false;
3064     bool doChannelMembersChangedSignal = false;
3065     QString lcNickname(nickname.toLower());
3066     // Create NickInfo if not already created.
3067     NickInfoPtr nickInfo = getNickInfo(nickname);
3068     if (!nickInfo)
3069     {
3070         nickInfo = new NickInfo(nickname, this);
3071         m_allNicks.insert(lcNickname, nickInfo);
3072         doWatchedNickChangedSignal = isWatchedNick(nickname);
3073     }
3074     // if nickinfo already exists update nickname, in case we created the nickinfo based
3075     // on e.g. an incorrectly capitalized ISON request
3076     else
3077         nickInfo->setNickname(nickname);
3078 
3079     // Move the channel from unjoined list (if present) to joined list.
3080     QString lcChannelName = channelName.toLower();
3081     ChannelNickMap *channel;
3082     if (m_unjoinedChannels.contains(lcChannelName))
3083     {
3084         channel = m_unjoinedChannels[lcChannelName];
3085         m_unjoinedChannels.remove(lcChannelName);
3086         m_joinedChannels.insert(lcChannelName, channel);
3087         doChannelJoinedSignal = true;
3088     }
3089     else
3090     {
3091         // Create a new list in the joined channels if not already present.
3092         if (!m_joinedChannels.contains(lcChannelName))
3093         {
3094             channel = new ChannelNickMap;
3095             m_joinedChannels.insert(lcChannelName, channel);
3096             doChannelJoinedSignal = true;
3097         }
3098         else
3099             channel = m_joinedChannels[lcChannelName];
3100     }
3101     // Add NickInfo to channel list if not already in the list.
3102     ChannelNickPtr channelNick;
3103     if (!channel->contains(lcNickname))
3104     {
3105         channelNick = new ChannelNick(nickInfo, lcChannelName);
3106         Q_ASSERT(channelNick);
3107         channel->insert(lcNickname, channelNick);
3108         doChannelMembersChangedSignal = true;
3109     }
3110     channelNick = (*channel)[lcNickname];
3111     Q_ASSERT(channelNick);                        //Since we just added it if it didn't exist, it should be guaranteed to exist now
3112     if (doWatchedNickChangedSignal) Q_EMIT watchedNickChanged(this, nickname, true);
3113     if (doChannelJoinedSignal) Q_EMIT channelJoinedOrUnjoined(this, channelName, true);
3114     if (doChannelMembersChangedSignal) Q_EMIT channelMembersChanged(this, channelName, true, false, nickname);
3115     return channelNick;
3116 }
3117 
3118 // Adds a nickname to the unjoinedChannels list.
3119 // Creates new NickInfo if necessary.
3120 // If needed, moves the channel from the joined list to the unjoined list.
3121 // If mode != 99 sets the mode for this nick in this channel.
3122 // Returns the NickInfo for the nickname.
addNickToUnjoinedChannelsList(const QString & channelName,const QString & nickname)3123 ChannelNickPtr Server::addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname)
3124 {
3125     bool doChannelUnjoinedSignal = false;
3126     bool doWatchedNickChangedSignal = false;
3127     bool doChannelMembersChangedSignal = false;
3128     QString lcNickname(nickname.toLower());
3129     // Create NickInfo if not already created.
3130     NickInfoPtr nickInfo = getNickInfo(nickname);
3131     if (!nickInfo)
3132     {
3133         nickInfo = new NickInfo(nickname, this);
3134         m_allNicks.insert(lcNickname, nickInfo);
3135         doWatchedNickChangedSignal = isWatchedNick(nickname);
3136     }
3137     // Move the channel from joined list (if present) to unjoined list.
3138     QString lcChannelName = channelName.toLower();
3139     ChannelNickMap *channel;
3140     if (m_joinedChannels.contains(lcChannelName))
3141     {
3142         channel = m_joinedChannels[lcChannelName];
3143         m_joinedChannels.remove(lcChannelName);
3144         m_unjoinedChannels.insert(lcChannelName, channel);
3145         doChannelUnjoinedSignal = true;
3146     }
3147     else
3148     {
3149         // Create a new list in the unjoined channels if not already present.
3150         if (!m_unjoinedChannels.contains(lcChannelName))
3151         {
3152             channel = new ChannelNickMap;
3153             m_unjoinedChannels.insert(lcChannelName, channel);
3154             doChannelUnjoinedSignal = true;
3155         }
3156         else
3157             channel = m_unjoinedChannels[lcChannelName];
3158     }
3159     // Add NickInfo to unjoinedChannels list if not already in the list.
3160     ChannelNickPtr channelNick;
3161     if (!channel->contains(lcNickname))
3162     {
3163         channelNick = new ChannelNick(nickInfo, lcChannelName);
3164         channel->insert(lcNickname, channelNick);
3165         doChannelMembersChangedSignal = true;
3166     }
3167     channelNick = (*channel)[lcNickname];
3168     // Set the mode for the nick in this channel.
3169     if (doWatchedNickChangedSignal) Q_EMIT watchedNickChanged(this, nickname, true);
3170     if (doChannelUnjoinedSignal) Q_EMIT channelJoinedOrUnjoined(this, channelName, false);
3171     if (doChannelMembersChangedSignal) Q_EMIT channelMembersChanged(this, channelName, false, false, nickname);
3172     return channelNick;
3173 }
3174 
3175 /**
3176  * If not already online, changes a nick to the online state by creating
3177  * a NickInfo for it and emits various signals and messages for it.
3178  * This method should only be called for nicks on the watch list.
3179  * @param nickname           The nickname that is online.
3180  * @return                   Pointer to NickInfo for nick.
3181  */
setWatchedNickOnline(const QString & nickname)3182 NickInfoPtr Server::setWatchedNickOnline(const QString& nickname)
3183 {
3184     NickInfoPtr nickInfo = getNickInfo(nickname);
3185     if (!nickInfo)
3186     {
3187         QString lcNickname(nickname.toLower());
3188         nickInfo = new NickInfo(nickname, this);
3189         m_allNicks.insert(lcNickname, nickInfo);
3190     }
3191 
3192     Q_EMIT watchedNickChanged(this, nickname, true);
3193 
3194     appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 is online (%2).", nickname, getServerName()), QHash<QString, QString>(), getStatusView());
3195 
3196     Application::instance()->notificationHandler()->nickOnline(getStatusView(), nickname);
3197 
3198     nickInfo->setPrintedOnline(true);
3199     return nickInfo;
3200 }
3201 
setWatchedNickOffline(const QString & nickname,const NickInfoPtr & nickInfo)3202 void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr &nickInfo)
3203 {
3204     Q_UNUSED(nickInfo)
3205 
3206     Q_EMIT watchedNickChanged(this, nickname, false);
3207 
3208     appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), QHash<QString, QString>(), getStatusView());
3209 
3210     Application::instance()->notificationHandler()->nickOffline(getStatusView(), nickname);
3211 
3212 }
3213 
setNickOffline(const QString & nickname)3214 bool Server::setNickOffline(const QString& nickname)
3215 {
3216     QString lcNickname(nickname.toLower());
3217     NickInfoPtr nickInfo = getNickInfo(lcNickname);
3218 
3219     bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false;
3220 
3221     if (wasOnline)
3222     {
3223         // Delete from query list, if present.
3224         if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname);
3225         // Delete the nickname from all channels (joined or unjoined).
3226         const QStringList nickChannels = getNickChannels(lcNickname);
3227 
3228         for (const QString& channel : nickChannels) {
3229             removeChannelNick(channel, lcNickname);
3230         }
3231 
3232         // Delete NickInfo.
3233         if (m_allNicks.contains(lcNickname)) m_allNicks.remove(lcNickname);
3234         // If the nick was in the watch list, emit various signals and messages.
3235         if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo);
3236 
3237         nickInfo->setPrintedOnline(false);
3238     }
3239 
3240     return (nickInfo != nullptr);
3241 }
3242 
3243 /**
3244  * If nickname is no longer on any channel list, or the query list, delete it altogether.
3245  * Call this routine only if the nick is not on the notify list or is on the notify
3246  * list but is known to be offline.
3247  * @param nickname           The nickname to be deleted.  Case insensitive.
3248  * @return                   True if the nickname is deleted.
3249  */
deleteNickIfUnlisted(const QString & nickname)3250 bool Server::deleteNickIfUnlisted(const QString &nickname)
3251 {
3252     QString lcNickname(nickname.toLower());
3253     // Don't delete our own nickinfo.
3254     if (lcNickname == loweredNickname()) return false;
3255 
3256     if (!m_queryNicks.contains(lcNickname))
3257     {
3258         QStringList nickChannels = getNickChannels(nickname);
3259         if (nickChannels.isEmpty())
3260         {
3261             m_allNicks.remove(lcNickname);
3262             return true;
3263         }
3264     }
3265     return false;
3266 }
3267 
3268 /**
3269  * Remove nickname from a channel (on joined or unjoined lists).
3270  * @param channelName The channel name.  Case insensitive.
3271  * @param nickname    The nickname.  Case insensitive.
3272  */
removeChannelNick(const QString & channelName,const QString & nickname)3273 void Server::removeChannelNick(const QString& channelName, const QString& nickname)
3274 {
3275     bool doSignal = false;
3276     bool joined = false;
3277     QString lcChannelName = channelName.toLower();
3278     QString lcNickname = nickname.toLower();
3279     ChannelNickMap *channel;
3280     if (m_joinedChannels.contains(lcChannelName))
3281     {
3282         channel = m_joinedChannels[lcChannelName];
3283         if (channel->contains(lcNickname))
3284         {
3285             channel->remove(lcNickname);
3286             doSignal = true;
3287             joined = true;
3288             // Note: Channel should not be empty because user's own nick should still be
3289             // in it, so do not need to delete empty channel here.
3290         }
3291         else
3292         {
3293             qCDebug(KONVERSATION_LOG) << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName;
3294         }
3295     }
3296     else
3297     {
3298         if (m_unjoinedChannels.contains(lcChannelName))
3299         {
3300             channel = m_unjoinedChannels[lcChannelName];
3301             if (channel->contains(lcNickname))
3302             {
3303                 channel->remove(lcNickname);
3304                 doSignal = true;
3305                 joined = false;
3306                 // If channel is now empty, delete it.
3307                 // Caution: Any iterators across unjoinedChannels will be come invalid here.
3308                 if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName);
3309             }
3310             else
3311             {
3312                 qCDebug(KONVERSATION_LOG) << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName;
3313             }
3314         }
3315     }
3316     if (doSignal) Q_EMIT channelMembersChanged(this, channelName, joined, true, nickname);
3317 }
3318 
getWatchList() const3319 QStringList Server::getWatchList() const
3320 {
3321     // no nickinfo ISON for the time being
3322     if (getServerGroup())
3323         return Preferences::notifyListByGroupId(getServerGroup()->id());
3324     else
3325         return QStringList();
3326 
3327     if (m_serverISON)
3328         return m_serverISON->getWatchList();
3329     else
3330         return QStringList();
3331 }
3332 
getISONList() const3333 QStringList Server::getISONList() const
3334 {
3335     // no nickinfo ISON for the time being
3336     if (getServerGroup())
3337         return Preferences::notifyListByGroupId(getServerGroup()->id());
3338     else
3339         return QStringList();
3340 
3341     if (m_serverISON)
3342         return m_serverISON->getISONList();
3343     else
3344         return QStringList();
3345 }
3346 
getISONListString() const3347 QString Server::getISONListString() const { return getISONList().join(QLatin1Char(' ')); }
3348 
3349 /**
3350  * Return true if the given nickname is on the watch list.
3351  */
isWatchedNick(const QString & nickname) const3352 bool Server::isWatchedNick(const QString& nickname) const
3353 {
3354     // no nickinfo ISON for the time being
3355     if (getServerGroup())
3356         return Preferences::isNotify(getServerGroup()->id(), nickname);
3357     else
3358         return false;
3359 
3360     // ###### ERROR: not reached
3361     return getWatchList().contains(nickname, Qt::CaseInsensitive);
3362 }
3363 
3364 /**
3365  * Remove channel from the joined list, placing it in the unjoined list.
3366  * All the unwatched nicks are removed from the channel.  If the channel becomes
3367  * empty, it is deleted.
3368  * @param channelName        Name of the channel.  Case sensitive.
3369  */
removeJoinedChannel(const QString & channelName)3370 void Server::removeJoinedChannel(const QString& channelName)
3371 {
3372     bool doSignal = false;
3373     QStringList watchListLower = getWatchList();
3374     QString lcChannelName = channelName.toLower();
3375     // Move the channel nick list from the joined to unjoined lists.
3376     if (m_joinedChannels.contains(lcChannelName))
3377     {
3378         doSignal = true;
3379         ChannelNickMap* channel = m_joinedChannels[lcChannelName];
3380         m_joinedChannels.remove(lcChannelName);
3381         m_unjoinedChannels.insert(lcChannelName, channel);
3382         // Remove nicks not on the watch list.
3383         bool allDeleted = true;
3384         Q_ASSERT(channel);
3385         if(!channel) return;                      //already removed.. hmm
3386         ChannelNickMap::Iterator member;
3387         for ( member = channel->begin(); member != channel->end() ;)
3388         {
3389             QString lcNickname = member.key();
3390             if (!watchListLower.contains(lcNickname))
3391             {
3392                 // Remove the unwatched nickname from the unjoined channel.
3393                 channel->erase(member);
3394                 // If the nick is no longer listed in any channels or query list, delete it altogether.
3395                 deleteNickIfUnlisted(lcNickname);
3396                 member = channel->begin();
3397             }
3398             else
3399             {
3400                 allDeleted = false;
3401                 ++member;
3402             }
3403         }
3404         // If all were deleted, remove the channel from the unjoined list.
3405         if (allDeleted)
3406         {
3407             channel = m_unjoinedChannels[lcChannelName];
3408             m_unjoinedChannels.remove(lcChannelName);
3409             delete channel;                       // recover memory!
3410         }
3411     }
3412     if (doSignal) Q_EMIT channelJoinedOrUnjoined(this, channelName, false);
3413 }
3414 
3415 // Renames a nickname in all NickInfo lists.
3416 // Returns pointer to the NickInfo object or 0 if nick not found.
renameNickInfo(NickInfoPtr nickInfo,const QString & newname)3417 void Server::renameNickInfo(NickInfoPtr nickInfo, const QString& newname)
3418 {
3419     if (nickInfo)
3420     {
3421         // Get existing lowercase nickname and rename nickname in the NickInfo object.
3422         QString lcNickname(nickInfo->loweredNickname());
3423         nickInfo->setNickname(newname);
3424         QString lcNewname(newname.toLower());
3425         // Rename the key in m_allNicks list.
3426         m_allNicks.remove(lcNickname);
3427         m_allNicks.insert(lcNewname, nickInfo);
3428         // Rename key in the joined and unjoined lists.
3429         const QStringList nickChannels = getNickChannels(lcNickname);
3430 
3431         for (const QString& channelName : nickChannels) {
3432             const ChannelNickMap *channel = getChannelMembers(channelName);
3433             Q_ASSERT(channel);
3434             ChannelNickPtr member = (*channel)[lcNickname];
3435             Q_ASSERT(member);
3436             const_cast<ChannelNickMap *>(channel)->remove(lcNickname);
3437             const_cast<ChannelNickMap *>(channel)->insert(lcNewname, member);
3438         }
3439 
3440         // Rename key in Query list.
3441         if (m_queryNicks.contains(lcNickname))
3442         {
3443             m_queryNicks.remove(lcNickname);
3444             m_queryNicks.insert(lcNewname, nickInfo);
3445         }
3446     }
3447     else
3448     {
3449         qCDebug(KONVERSATION_LOG) << "was called for newname='" << newname << "' but nickInfo is null";
3450     }
3451 }
3452 
nickJoinsChannel(const QString & channelName,const QString & nickname,const QString & hostmask,const QString & account,const QString & realName,const QHash<QString,QString> & messageTags)3453 Channel* Server::nickJoinsChannel(const QString &channelName, const QString &nickname, const QString &hostmask, const QString &account,
3454                                   const QString &realName, const QHash<QString, QString> &messageTags)
3455 {
3456     Channel* outChannel = getChannelByName(channelName);
3457     if(outChannel)
3458     {
3459         // Update NickInfo.
3460         ChannelNickPtr channelNick = addNickToJoinedChannelsList(channelName, nickname);
3461         NickInfoPtr nickInfo = channelNick->getNickInfo();
3462         if ((nickInfo->getHostmask() != hostmask) && !hostmask.isEmpty())
3463         {
3464             nickInfo->setHostmask(hostmask);
3465         }
3466         if (!account.isEmpty())
3467         {
3468             nickInfo->setAccount(account);
3469         }
3470         if (!realName.isEmpty())
3471         {
3472             nickInfo->setRealName(realName);
3473         }
3474         outChannel->joinNickname(channelNick, messageTags);
3475     }
3476 
3477     return outChannel;
3478 }
3479 
addHostmaskToNick(const QString & sourceNick,const QString & sourceHostmask)3480 void Server::addHostmaskToNick(const QString& sourceNick, const QString& sourceHostmask)
3481 {
3482     // Update NickInfo.
3483     NickInfoPtr nickInfo = getNickInfo(sourceNick);
3484     if (nickInfo)
3485     {
3486         if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty())
3487         {
3488             nickInfo->setHostmask(sourceHostmask);
3489         }
3490     }
3491 }
3492 
removeNickFromChannel(const QString & channelName,const QString & nickname,const QString & reason,const QHash<QString,QString> & messageTags,bool quit)3493 Channel* Server::removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash<QString, QString> &messageTags, bool quit)
3494 {
3495     Channel* outChannel = getChannelByName(channelName);
3496     if(outChannel)
3497     {
3498         outChannel->flushNickQueue();
3499         ChannelNickPtr channelNick = getChannelNick(channelName, nickname);
3500         if(channelNick)
3501         {
3502             outChannel->removeNick(channelNick,reason,quit, messageTags);
3503         }
3504     }
3505 
3506     // Remove the nick from the channel.
3507     removeChannelNick(channelName, nickname);
3508     // If not listed in any channel, and not on query list, delete the NickInfo,
3509     // but only if not on the notify list.  ISON replies will take care of deleting
3510     // the NickInfo, if on the notify list.
3511     if (!isWatchedNick(nickname))
3512     {
3513         QString nicky = nickname;
3514         deleteNickIfUnlisted(nicky);
3515     }
3516 
3517     return outChannel;
3518 }
3519 
nickWasKickedFromChannel(const QString & channelName,const QString & nickname,const QString & kicker,const QString & reason,const QHash<QString,QString> & messageTags)3520 void Server::nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash<QString, QString> &messageTags)
3521 {
3522     Channel* outChannel = getChannelByName(channelName);
3523     if(outChannel)
3524     {
3525         outChannel->flushNickQueue();
3526         ChannelNickPtr channelNick = getChannelNick(channelName, nickname);
3527 
3528         if(channelNick)
3529         {
3530           outChannel->kickNick(channelNick, kicker, reason, messageTags);
3531           // Tell Nickinfo
3532           removeChannelNick(channelName,nickname);
3533         }
3534     }
3535 }
3536 
removeNickFromServer(const QString & nickname,const QString & reason,const QHash<QString,QString> & messageTags)3537 void Server::removeNickFromServer(const QString &nickname,const QString &reason, const QHash<QString, QString> &messageTags)
3538 {
3539     for (Channel* channel : qAsConst(m_channelList)) {
3540         channel->flushNickQueue();
3541         // Check if nick is in this channel or not.
3542         if(channel->getNickByName(nickname))
3543             removeNickFromChannel(channel->getName(), nickname, reason, messageTags, true);
3544     }
3545 
3546     Query* query = getQueryByName(nickname);
3547     if (query) query->quitNick(reason, messageTags);
3548 
3549     // Delete the nick from all channels and then delete the nickinfo,
3550     // emitting signal if on the watch list.
3551     setNickOffline(nickname);
3552 }
3553 
renameNick(const QString & nickname,const QString & newNick,const QHash<QString,QString> & messageTags)3554 void Server::renameNick(const QString &nickname, const QString &newNick, const QHash<QString, QString> &messageTags)
3555 {
3556     if(nickname.isEmpty() || newNick.isEmpty())
3557     {
3558         qCDebug(KONVERSATION_LOG) << "called with empty strings!  Trying to rename '" << nickname << "' to '" << newNick << "'";
3559         return;
3560     }
3561 
3562     // If this was our own nickchange, tell our server object about it
3563     if (nickname == getNickname())
3564         setNickname(newNick);
3565 
3566     //Actually do the rename.
3567     NickInfoPtr nickInfo = getNickInfo(nickname);
3568 
3569     if(!nickInfo)
3570     {
3571         qCDebug(KONVERSATION_LOG) << "called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results.";
3572     }
3573     else
3574     {
3575         renameNickInfo(nickInfo, newNick);
3576         //The rest of the code below allows the channels to echo to the user to tell them that the nick has changed.
3577 
3578         // Rename the nick in every channel they are in
3579         for (Channel* channel : qAsConst(m_channelList)) {
3580             channel->flushNickQueue();
3581 
3582             // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet
3583             if (channel->getNickByName(nickname)) channel->nickRenamed(nickname, *nickInfo, messageTags);
3584         }
3585 
3586         //Watched nicknames stuff
3587         if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, NickInfoPtr());
3588     }
3589 
3590     // We had an encrypt conversation with the user that changed his nick, lets copy the key to the new nick and remove the old nick
3591     #ifdef HAVE_QCA2
3592     QByteArray userKey = getKeyForRecipient(nickname);
3593 
3594     if (!userKey.isEmpty())
3595     {
3596         setKeyForRecipient(newNick, userKey);
3597         m_keyHash.remove(nickname.toLower());
3598     }
3599     #endif
3600 
3601 }
3602 
userhost(const QString & nick,const QString & hostmask,bool away,bool)3603 void Server::userhost(const QString& nick,const QString& hostmask,bool away,bool /* ircOp */)
3604 {
3605     addHostmaskToNick(nick, hostmask);
3606     // remember my IP for DCC things
3607                                                   // myself
3608     if (m_ownIpByUserhost.isEmpty() && nick == getNickname())
3609     {
3610         QString myhost = hostmask.section(QLatin1Char('@'), 1);
3611         // Use async lookup else you will be blocking GUI badly
3612         QHostInfo::lookupHost(myhost, this, &Server::gotOwnResolvedHostByUserhost);
3613     }
3614     NickInfoPtr nickInfo = getNickInfo(nick);
3615     if (nickInfo)
3616     {
3617         if (nickInfo->isAway() != away)
3618         {
3619             nickInfo->setAway(away);
3620         }
3621     }
3622 }
3623 
gotOwnResolvedHostByUserhost(const QHostInfo & res)3624 void Server::gotOwnResolvedHostByUserhost(const QHostInfo& res)
3625 {
3626     if ( res.error() == QHostInfo::NoError && !res.addresses().isEmpty() )
3627         m_ownIpByUserhost = res.addresses().first().toString();
3628     else
3629         qCDebug(KONVERSATION_LOG) << "Got error: " << res.errorString();
3630 }
3631 
appendServerMessageToChannel(const QString & channel,const QString & type,const QString & message,const QHash<QString,QString> & messageTags)3632 void Server::appendServerMessageToChannel(const QString& channel,const QString& type,const QString& message, const QHash<QString, QString> &messageTags)
3633 {
3634     Channel* outChannel = getChannelByName(channel);
3635     if (outChannel) outChannel->appendServerMessage(type, message, messageTags);
3636 }
3637 
appendCommandMessageToChannel(const QString & channel,const QString & command,const QString & message,const QHash<QString,QString> & messageTags,bool highlight,bool parseURL)3638 void Server::appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash<QString, QString> &messageTags, bool highlight, bool parseURL)
3639 {
3640     Channel* outChannel = getChannelByName(channel);
3641     if (outChannel)
3642     {
3643         outChannel->appendCommandMessage(command, message, messageTags, parseURL, !highlight);
3644     }
3645     else
3646     {
3647         appendStatusMessage(command, QStringLiteral("%1 %2").arg(channel, message), messageTags);
3648     }
3649 }
3650 
appendStatusMessage(const QString & type,const QString & message,const QHash<QString,QString> & messageTags)3651 void Server::appendStatusMessage(const QString& type, const QString& message, const QHash<QString, QString> &messageTags)
3652 {
3653     getStatusView()->appendServerMessage(type, message, messageTags);
3654 }
3655 
appendMessageToFrontmost(const QString & type,const QString & message,const QHash<QString,QString> & messageTags,bool parseURL)3656 void Server::appendMessageToFrontmost(const QString& type, const QString& message, const QHash<QString, QString> &messageTags, bool parseURL)
3657 {
3658     getViewContainer()->appendToFrontmost(type, message, getStatusView(), messageTags, parseURL);
3659 }
3660 
setNickname(const QString & newNickname)3661 void Server::setNickname(const QString &newNickname)
3662 {
3663     m_nickname = newNickname;
3664     m_loweredNickname = newNickname.toLower();
3665     if (!m_nickListModel->stringList().contains(newNickname)) {
3666         m_nickListModel->insertRows(m_nickListModel->rowCount(), 1);
3667         m_nickListModel->setData(m_nickListModel->index(m_nickListModel->rowCount() -1 , 0), newNickname, Qt::DisplayRole);
3668     }
3669     Q_EMIT nicknameChanged(newNickname);
3670 }
3671 
setChannelTopic(const QString & channel,const QString & newTopic,const QHash<QString,QString> & messageTags)3672 void Server::setChannelTopic(const QString &channel, const QString &newTopic, const QHash<QString, QString> &messageTags)
3673 {
3674     Channel* outChannel = getChannelByName(channel);
3675     if(outChannel)
3676     {
3677         // encoding stuff is done in send()
3678         outChannel->setTopic(newTopic, messageTags);
3679     }
3680 }
3681 
3682                                                   // Overloaded
setChannelTopic(const QString & nickname,const QString & channel,const QString & newTopic,const QHash<QString,QString> & messageTags)3683 void Server::setChannelTopic(const QString& nickname, const QString &channel, const QString &newTopic, const QHash<QString, QString> &messageTags)
3684 {
3685     Channel* outChannel = getChannelByName(channel);
3686     if(outChannel)
3687     {
3688         // encoding stuff is done in send()
3689         outChannel->setTopic(nickname, newTopic, messageTags);
3690     }
3691 }
3692 
setTopicAuthor(const QString & channel,const QString & author,const QDateTime & time)3693 void Server::setTopicAuthor(const QString& channel, const QString& author, const QDateTime &time)
3694 {
3695     Channel* outChannel = getChannelByName(channel);
3696     if(outChannel)
3697         outChannel->setTopicAuthor(author, time);
3698 }
3699 
endOfWho(const QString & target)3700 void Server::endOfWho(const QString& target)
3701 {
3702     Channel* channel = getChannelByName(target);
3703     if(channel)
3704         channel->scheduleAutoWho();
3705 }
3706 
endOfNames(const QString & target)3707 void Server::endOfNames(const QString& target)
3708 {
3709     Channel* channel = getChannelByName(target);
3710     if(channel)
3711         channel->endOfNames();
3712 }
3713 
isNickname(const QString & compare) const3714 bool Server::isNickname(const QString &compare) const
3715 {
3716     return (m_nickname == compare);
3717 }
3718 
getNickname() const3719 QString Server::getNickname() const
3720 {
3721     return m_nickname;
3722 }
3723 
loweredNickname() const3724 QString Server::loweredNickname() const
3725 {
3726     return m_loweredNickname;
3727 }
3728 
parseWildcards(const QString & toParse,ChatWindow * context,const QStringList & nicks)3729 QString Server::parseWildcards(const QString& toParse, ChatWindow* context, const QStringList &nicks)
3730 {
3731     QString inputLineText;
3732 
3733     if (context && context->getInputBar())
3734         inputLineText = context->getInputBar()->toPlainText();
3735 
3736     if (!context)
3737         return parseWildcards(toParse, getNickname(), QString(), QString(), QString(), QString());
3738     else if (context->getType() == ChatWindow::Channel)
3739     {
3740         auto* channel = static_cast<Channel*>(context);
3741 
3742         return parseWildcards(toParse, getNickname(), context->getName(), channel->getPassword(),
3743             !nicks.isEmpty() ? nicks : channel->getSelectedNickList(), inputLineText);
3744     }
3745     else if (context->getType() == ChatWindow::Query)
3746         return parseWildcards(toParse, getNickname(), context->getName(), QString(), context->getName(), inputLineText);
3747 
3748     return parseWildcards(toParse, getNickname(), context->getName(), QString(), QString(), inputLineText);
3749 }
3750 
parseWildcards(const QString & toParse,const QString & sender,const QString & channelName,const QString & channelKey,const QString & nick,const QString & inputLineText)3751 QString Server::parseWildcards(const QString& toParse,
3752 const QString& sender,
3753 const QString& channelName,
3754 const QString& channelKey,
3755 const QString& nick,
3756 const QString& inputLineText)
3757 {
3758 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3759     return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), QString::SkipEmptyParts), inputLineText);
3760 #else
3761     return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), Qt::SkipEmptyParts), inputLineText);
3762 #endif
3763 }
3764 
parseWildcards(const QString & toParse,const QString & sender,const QString & channelName,const QString & channelKey,const QStringList & nickList,const QString & inputLineText)3765 QString Server::parseWildcards(const QString& toParse,
3766 const QString& sender,
3767 const QString& channelName,
3768 const QString& channelKey,
3769 const QStringList& nickList,
3770 const QString& inputLineText
3771 )
3772 {
3773     // store the parsed version
3774     QString out;
3775 
3776     // default separator
3777     QString separator(QStringLiteral(" "));
3778 
3779     int index = 0, found = 0;
3780     QChar toExpand;
3781 
3782     while ((found = toParse.indexOf(QLatin1Char('%'), index)) != -1)
3783     {
3784                                                   // append part before the %
3785         out.append(toParse.midRef(index,found-index));
3786         index = found + 1;                        // skip the part before, including %
3787         if (index >= (int)toParse.length())
3788             break;                                // % was the last char (not valid)
3789         toExpand = toParse.at(index++);
3790         if (toExpand == QLatin1Char('s'))
3791         {
3792             found = toParse.indexOf(QLatin1Char('%'), index);
3793             if (found == -1)                      // no other % (not valid)
3794                 break;
3795             separator = toParse.mid(index,found-index);
3796             index = found + 1;                    // skip separator, including %
3797         }
3798         else if (toExpand == QLatin1Char('u'))
3799         {
3800             out.append(nickList.join(separator));
3801         }
3802         else if (toExpand == QLatin1Char('c'))
3803         {
3804             if(!channelName.isEmpty())
3805                 out.append(channelName);
3806         }
3807         else if (toExpand == QLatin1Char('o'))
3808         {
3809             out.append(sender);
3810         }
3811         else if (toExpand == QLatin1Char('k'))
3812         {
3813             if(!channelKey.isEmpty())
3814                 out.append(channelKey);
3815         }
3816         else if (toExpand == QLatin1Char('K'))
3817         {
3818             if(getConnectionSettings().server().password().isEmpty())
3819                 out.append(getConnectionSettings().server().password());
3820         }
3821         else if (toExpand == QLatin1Char('n'))
3822         {
3823             out.append(QLatin1Char('\n'));
3824         }
3825         else if (toExpand == QLatin1Char('p'))
3826         {
3827             out.append(QLatin1Char('%'));
3828         }
3829         else if (toExpand == QLatin1Char('i'))
3830         {
3831             out.append(inputLineText);
3832         }
3833     }
3834 
3835                                                   // append last part
3836     out.append(toParse.midRef(index,toParse.length()-index));
3837     return out;
3838 }
3839 
sendToAllChannels(const QString & text)3840 void Server::sendToAllChannels(const QString &text)
3841 {
3842     // Send a message to all channels we are in
3843     for (Channel* channel : qAsConst(m_channelList)) {
3844         channel->sendText(text);
3845     }
3846 }
3847 
invitation(const QString & nick,const QString & channel)3848 void Server::invitation(const QString& nick,const QString& channel)
3849 {
3850     if(!m_inviteDialog)
3851     {
3852         QDialogButtonBox::StandardButton buttonCode = QDialogButtonBox::Cancel;
3853 
3854         if(!InviteDialog::shouldBeShown(buttonCode))
3855         {
3856             if (buttonCode == QDialogButtonBox::Ok)
3857                 sendJoinCommand(channel);
3858 
3859             return;
3860         }
3861 
3862         m_inviteDialog = new InviteDialog (getViewContainer()->getWindow());
3863         connect(m_inviteDialog, &InviteDialog::joinChannelsRequested,
3864                 this, [this](const QString& channels) { sendJoinCommand(channels); });
3865     }
3866 
3867     m_inviteDialog->show();
3868     m_inviteDialog->raise();
3869 
3870     m_inviteDialog->addInvite(nick, channel);
3871 }
3872 
scriptNotFound(const QString & name)3873 void Server::scriptNotFound(const QString& name)
3874 {
3875     appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not find script \"%1\".", name));
3876 }
3877 
scriptExecutionError(const QString & name)3878 void Server::scriptExecutionError(const QString& name)
3879 {
3880     appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not execute script \"%1\". Check file permissions.", name));
3881 }
3882 
isAChannel(const QString & channel) const3883 bool Server::isAChannel(const QString &channel) const
3884 {
3885     if (channel.isEmpty()) return false;
3886 
3887     QRegularExpressionMatch x = m_targetMatcher.match(channel);
3888     int index = x.capturedStart(2);
3889 
3890     return (index >= 0 && getChannelTypes().contains(channel.at(index)));
3891 }
3892 
addRawLog(bool show)3893 void Server::addRawLog(bool show)
3894 {
3895     if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this);
3896 
3897     connect(this, &Server::serverOnline, m_rawLog, &RawLog::serverOnline);
3898 
3899     // bring raw log to front since the main window does not do this for us
3900     if (show) Q_EMIT showView(m_rawLog);
3901 }
3902 
closeRawLog()3903 void Server::closeRawLog()
3904 {
3905     delete m_rawLog;
3906 }
3907 
requestOpenChannelListPanel(const QString & filter)3908 void Server::requestOpenChannelListPanel(const QString& filter)
3909 {
3910     getViewContainer()->openChannelList(this, filter, true);
3911 }
3912 
addChannelListPanel()3913 ChannelListPanel* Server::addChannelListPanel()
3914 {
3915     if(!m_channelListPanel)
3916     {
3917         m_channelListPanel = getViewContainer()->addChannelListPanel(this);
3918 
3919         connect(&m_inputFilter, &InputFilter::endOfChannelList, m_channelListPanel.data(), &ChannelListPanel::endOfChannelList);
3920         connect(m_channelListPanel.data(), &ChannelListPanel::refreshChannelList, this, &Server::requestChannelList);
3921         connect(m_channelListPanel, &ChannelListPanel::joinChannel, this, [this](const QString& name) { sendJoinCommand(name); });
3922         connect(this, &Server::serverOnline, m_channelListPanel, &ChannelListPanel::serverOnline);
3923     }
3924 
3925     return m_channelListPanel;
3926 }
3927 
addToChannelList(const QString & channel,int users,const QString & topic)3928 void Server::addToChannelList(const QString& channel, int users, const QString& topic)
3929 {
3930     addChannelListPanel();
3931     m_channelListPanel->addToChannelList(channel, users, topic);
3932 }
3933 
getChannelListPanel() const3934 ChannelListPanel* Server::getChannelListPanel() const
3935 {
3936     return m_channelListPanel;
3937 }
3938 
closeChannelListPanel()3939 void Server::closeChannelListPanel()
3940 {
3941     delete m_channelListPanel;
3942 }
3943 
updateAutoJoin(Konversation::ChannelList channels)3944 void Server::updateAutoJoin(Konversation::ChannelList channels)
3945 {
3946     Konversation::ChannelList tmpList;
3947 
3948     if (!channels.isEmpty())
3949     {
3950         tmpList.reserve(channels.size());
3951         for (const ChannelSettings& cs : qAsConst(channels)) {
3952             tmpList << cs;
3953         }
3954     }
3955     else if (m_channelList.isEmpty() && getServerGroup())
3956         tmpList = getServerGroup()->channelList();
3957     else
3958     {
3959         tmpList.reserve(m_channelList.size());
3960         for (Channel* channel : qAsConst(m_channelList)) {
3961             tmpList << channel->channelSettings();
3962         }
3963     }
3964 
3965     if (!tmpList.isEmpty())
3966     {
3967         setAutoJoinCommands(generateJoinCommand(tmpList));
3968         setAutoJoin(!m_autoJoinCommands.isEmpty());
3969     }
3970     else
3971     {
3972         m_autoJoinCommands.clear();
3973         setAutoJoin(false);
3974     }
3975 }
3976 
3977 
generateJoinCommand(const Konversation::ChannelList & tmpList)3978 QStringList Server::generateJoinCommand(const Konversation::ChannelList &tmpList)
3979 {
3980     QStringList channels;
3981     QStringList passwords;
3982     QStringList joinCommands;
3983     uint length = 0;
3984 
3985     for (const auto& channel : tmpList) {
3986         const QString channelName = channel.name();
3987 
3988         // Only add the channel to the JOIN command if it has a valid channel name.
3989         if (isAChannel(channelName)) {
3990             const QString password = channel.password().isEmpty() ? QStringLiteral(".") : channel.password();
3991 
3992             uint currentLength = getIdentity()->getCodec()->fromUnicode(channelName).length();
3993             currentLength += getIdentity()->getCodec()->fromUnicode(password).length();
3994 
3995             //channels.count() and passwords.count() account for the commas
3996             if (length + currentLength + 6 + channels.count() + passwords.count() >= 512) // 6: "JOIN " plus separating space between chans and pws.
3997             {
3998                 while (!passwords.isEmpty() && passwords.last() == QLatin1Char('.')) passwords.pop_back();
3999 
4000                 joinCommands << QStringLiteral("JOIN ") + channels.join(QLatin1Char(',')) + QLatin1Char(' ') + passwords.join(QLatin1Char(','));
4001 
4002                 channels.clear();
4003                 passwords.clear();
4004 
4005                 length = 0;
4006             }
4007 
4008             length += currentLength;
4009 
4010             channels << channelName;
4011             passwords << password;
4012         }
4013     }
4014 
4015     while (!passwords.isEmpty() && passwords.last() == QLatin1Char('.')) passwords.pop_back();
4016 
4017     // Even if the given tmpList contained entries they might have been filtered
4018     // out by the isAChannel() check.
4019     if (!channels.isEmpty())
4020     {
4021         joinCommands << QStringLiteral("JOIN ") + channels.join(QLatin1Char(',')) + QLatin1Char(' ') + passwords.join(QLatin1Char(','));
4022     }
4023 
4024     return joinCommands;
4025 }
4026 
getViewContainer() const4027 ViewContainer* Server::getViewContainer() const
4028 {
4029     Application* konvApp = Application::instance();
4030     return konvApp->getMainWindow()->getViewContainer();
4031 }
4032 
4033 
getUseSSL() const4034 bool Server::getUseSSL() const
4035 {
4036         if ( m_socket )
4037             return ( m_socket->mode() != QSslSocket::UnencryptedMode );
4038         else
4039             return false;
4040 }
4041 
4042 
getSSLInfo() const4043 QString Server::getSSLInfo() const
4044 {
4045 //     SSLSocket* sslsocket = dynamic_cast<SSLSocket*>(m_socket);
4046 
4047 //     if(sslsocket)
4048 //         return sslsocket->details();
4049 
4050     return QString();
4051 }
4052 
4053 
sendMultiServerCommand(const QString & command,const QString & parameter)4054 void Server::sendMultiServerCommand(const QString& command, const QString& parameter)
4055 {
4056     Q_EMIT multiServerCommand(command, parameter);
4057 }
4058 
executeMultiServerCommand(const QString & command,const QString & parameter)4059 void Server::executeMultiServerCommand(const QString& command, const QString& parameter)
4060 {
4061     if (command == QLatin1String("msg"))
4062         sendToAllChannelsAndQueries(parameter);
4063     else
4064         sendToAllChannelsAndQueries(Preferences::self()->commandChar() + command + QLatin1Char(' ') + parameter);
4065 }
4066 
sendToAllChannelsAndQueries(const QString & text)4067 void Server::sendToAllChannelsAndQueries(const QString& text)
4068 {
4069     // Send a message to all channels we are in
4070     for (Channel* channel : qAsConst(m_channelList)) {
4071         channel->sendText(text);
4072     }
4073 
4074     // Send a message to all queries we are in
4075     for (Query* query : qAsConst(m_queryList)) {
4076         query->sendText(text);
4077     }
4078 }
4079 
requestAway(const QString & reason)4080 void Server::requestAway(const QString& reason)
4081 {
4082     QString awayReason = reason;
4083 
4084     IdentityPtr identity = getIdentity();
4085 
4086     if (awayReason.isEmpty() && identity)
4087         awayReason = identity->getAwayMessage();
4088 
4089     // Fallback in case the identity has no away message set.
4090     if (awayReason.isEmpty())
4091         awayReason = i18n("Gone away for now");
4092 
4093     setAwayReason(awayReason);
4094 
4095     queue(QStringLiteral("AWAY :") + awayReason);
4096 }
4097 
requestUnaway()4098 void Server::requestUnaway()
4099 {
4100     queue(QStringLiteral("AWAY"));
4101 }
4102 
setAway(bool away,const QHash<QString,QString> & messageTags)4103 void Server::setAway(bool away, const QHash<QString, QString> &messageTags)
4104 {
4105     IdentityPtr identity = getIdentity();
4106 
4107     if (away)
4108     {
4109         if (!m_away) startAwayTimer();
4110 
4111         m_away = true;
4112 
4113         Q_EMIT awayState(true);
4114 
4115         if (identity && !identity->getAwayNickname().isEmpty() && identity->getAwayNickname() != getNickname())
4116         {
4117             m_nonAwayNick = getNickname();
4118             queue(QStringLiteral("NICK ") + getIdentity()->getAwayNickname());
4119         }
4120 
4121         if (!m_awayReason.isEmpty())
4122             appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away (reason: %1).",m_awayReason), messageTags);
4123         else
4124            appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away."), messageTags);
4125 
4126         if (identity && identity->getRunAwayCommands())
4127         {
4128             QString message = identity->getAwayCommand();
4129             const QRegularExpression re(QStringLiteral("%s"), QRegularExpression::CaseInsensitiveOption);
4130             sendToAllChannels(message.replace(re, m_awayReason));
4131         }
4132 
4133         if (identity && identity->getInsertRememberLineOnAway())
4134             Q_EMIT awayInsertRememberLine(this);
4135     }
4136     else
4137     {
4138         m_awayReason.clear();
4139 
4140         Q_EMIT awayState(false);
4141 
4142         if (!identity->getAwayNickname().isEmpty() && !m_nonAwayNick.isEmpty())
4143         {
4144             queue(QStringLiteral("NICK ") + m_nonAwayNick);
4145             m_nonAwayNick.clear();
4146         }
4147 
4148         if (m_away)
4149         {
4150             appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away."), messageTags);
4151 
4152             if (identity && identity->getRunAwayCommands())
4153             {
4154                 QString message = identity->getReturnCommand();
4155                 const QRegularExpression re(QStringLiteral("%t"), QRegularExpression::CaseInsensitiveOption);
4156                 sendToAllChannels(message.replace(re, awayTime()));
4157             }
4158         }
4159         else
4160             appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away."), messageTags);
4161 
4162         m_away = false;
4163     }
4164 }
4165 
awayTime() const4166 QString Server::awayTime() const
4167 {
4168     QString retVal;
4169 
4170     if (m_away)
4171     {
4172         int diff = QDateTime::currentDateTime().toSecsSinceEpoch() - m_awayTime;
4173         int num = diff / 3600;
4174 
4175         if (num < 10)
4176             retVal = QLatin1Char('0') + QString::number(num) + QLatin1Char(':');
4177         else
4178             retVal = QString::number(num) + QLatin1Char(':');
4179 
4180         num = (diff % 3600) / 60;
4181 
4182         if (num < 10) retVal += QLatin1Char('0');
4183 
4184         retVal += QString::number(num) + QLatin1Char(':');
4185 
4186         num = (diff % 3600) % 60;
4187 
4188         if (num < 10) retVal += QLatin1Char('0');
4189 
4190         retVal += QString::number(num);
4191     }
4192     else
4193         retVal = QStringLiteral("00:00:00");
4194 
4195     return retVal;
4196 }
4197 
startAwayTimer()4198 void Server::startAwayTimer()
4199 {
4200     m_awayTime = QDateTime::currentDateTime().toSecsSinceEpoch();
4201 }
4202 
enableIdentifyMsg(bool enabled)4203 void Server::enableIdentifyMsg(bool enabled)
4204 {
4205     m_identifyMsg = enabled;
4206 }
4207 
identifyMsgEnabled()4208 bool Server::identifyMsgEnabled()
4209 {
4210     return m_identifyMsg;
4211 }
4212 
addBan(const QString & channel,const QString & ban)4213 void Server::addBan(const QString &channel, const QString &ban)
4214 {
4215     Channel* outChannel = getChannelByName(channel);
4216     if(outChannel)
4217     {
4218         outChannel->addBan(ban);
4219     }
4220 }
4221 
removeBan(const QString & channel,const QString & ban)4222 void Server::removeBan(const QString &channel, const QString &ban)
4223 {
4224     Channel* outChannel = getChannelByName(channel);
4225     if(outChannel)
4226     {
4227         outChannel->removeBan(ban);
4228     }
4229 }
4230 
sendPing()4231 void Server::sendPing()
4232 {
4233     //WHO ourselves once a minute in case the irc server has changed our
4234     //hostmask, such as what happens when a Freenode cloak is activated.
4235     //It might be more intelligent to only do this when there is text
4236     //in the inputbox. Kinda changes this into a "do minutely"
4237     //queue :-)
4238     QStringList ql;
4239     ql << QStringLiteral("PING LAG") + QTime::currentTime().toString(QStringLiteral("hhmmss"));
4240     if(!m_whoRequestsDisabled)
4241     {
4242         getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true);
4243         ql << QStringLiteral("WHO ") + getNickname();
4244     }
4245     queueList(ql, HighPriority);
4246 
4247     m_lagTime.start();
4248     m_inputFilter.setLagMeasuring(true);
4249     m_pingResponseTimer.start(1000 /*1 sec*/);
4250 }
4251 
pongReceived()4252 void Server::pongReceived()
4253 {
4254     // ignore unrequested PONGs
4255     if (m_pingSendTimer.isActive())
4256         return;
4257 
4258     m_currentLag = m_lagTime.elapsed();
4259     m_inputFilter.setLagMeasuring(false);
4260     m_pingResponseTimer.stop();
4261 
4262     Q_EMIT serverLag(this, m_currentLag);
4263 
4264     // Send another PING in 60 seconds
4265     m_pingSendTimer.start(60000 /*60 sec*/);
4266 }
4267 
updateLongPongLag()4268 void Server::updateLongPongLag()
4269 {
4270     if (isSocketConnected())
4271     {
4272         m_currentLag = m_lagTime.elapsed();
4273         Q_EMIT tooLongLag(this, m_currentLag);
4274         // qCDebug(KONVERSATION_LOG) << "Current lag: " << currentLag;
4275 
4276         if (m_currentLag > (Preferences::self()->maximumLagTime() * 1000))
4277             m_socket->close();
4278     }
4279 }
4280 
updateEncoding()4281 void Server::updateEncoding()
4282 {
4283     if(getViewContainer() && getViewContainer()->getFrontView())
4284         getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView());
4285 }
4286 
4287 #ifdef HAVE_QCA2
initKeyExchange(const QString & receiver)4288 void Server::initKeyExchange(const QString &receiver)
4289 {
4290     Query* query;
4291     if (getQueryByName(receiver))
4292     {
4293         query = getQueryByName(receiver);
4294     }
4295     else
4296     {
4297         NickInfoPtr nickinfo = obtainNickInfo(receiver);
4298         query = addQuery(nickinfo, true);
4299     }
4300 
4301     Konversation::Cipher* cipher = query->getCipher();
4302 
4303     QByteArray pubKey = cipher->initKeyExchange();
4304     if(pubKey.isEmpty())
4305     {
4306         appendMessageToFrontmost(i18n("Error"), i18n("Failed to initiate key exchange with %1.", receiver));
4307     }
4308     else
4309     {
4310         queue(QLatin1String("NOTICE ") + receiver + QLatin1String(" :DH1080_INIT ") + QLatin1String(pubKey));
4311     }
4312 }
4313 
parseInitKeyX(const QString & sender,const QString & remoteKey)4314 void Server::parseInitKeyX(const QString &sender, const QString &remoteKey)
4315 {
4316     if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH))
4317     {
4318         appendMessageToFrontmost(i18n("Error"), i18n("Unable to perform key exchange with %1.", sender)
4319             + QLatin1Char(' ') + Konversation::Cipher::runtimeError());
4320 
4321         return;
4322     }
4323 
4324     //TODO ask the user to accept without blocking
4325     Query* query;
4326     if (getQueryByName(sender))
4327     {
4328         query = getQueryByName(sender);
4329     }
4330     else
4331     {
4332         NickInfoPtr nickinfo = obtainNickInfo(sender);
4333         query = addQuery(nickinfo, false);
4334     }
4335 
4336     Konversation::Cipher* cipher = query->getCipher();
4337 
4338     QByteArray pubKey = cipher->parseInitKeyX(remoteKey.toLocal8Bit());
4339 
4340     if (pubKey.isEmpty())
4341     {
4342         appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse the DH1080_INIT of %1. Key exchange failed.", sender));
4343     }
4344     else
4345     {
4346         setKeyForRecipient(sender, cipher->key());
4347         query->setEncryptedOutput(true);
4348         appendMessageToFrontmost(i18n("Notice"), i18n("Your key is set and your messages will now be encrypted, sending DH1080_FINISH to %1.", sender));
4349         queue(QLatin1String("NOTICE ") + sender + QLatin1String(" :DH1080_FINISH ") + QLatin1String(pubKey));
4350     }
4351 }
4352 
parseFinishKeyX(const QString & sender,const QString & remoteKey)4353 void Server::parseFinishKeyX(const QString &sender, const QString &remoteKey)
4354 {
4355     Query* query;
4356     if (getQueryByName(sender))
4357     {
4358         query = getQueryByName(sender);
4359     }
4360     else
4361         return;
4362 
4363     if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH))
4364     {
4365         appendMessageToFrontmost(i18n("Error"), i18n("Unable to complete key exchange with %1.", sender)
4366             + QLatin1Char(' ') + Konversation::Cipher::runtimeError());
4367 
4368         return;
4369     }
4370 
4371     Konversation::Cipher* cipher = query->getCipher();
4372 
4373     if (cipher->parseFinishKeyX(remoteKey.toLocal8Bit()))
4374     {
4375         setKeyForRecipient(sender,cipher->key());
4376         query->setEncryptedOutput(true);
4377         appendMessageToFrontmost(i18n("Notice"), i18n("Successfully parsed DH1080_FINISH sent by %1. Your key is set and your messages will now be encrypted.", sender));
4378     }
4379     else
4380     {
4381         appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse DH1080_FINISH sent by %1. Key exchange failed.", sender));
4382     }
4383 }
4384 #endif
4385 
nickListModel() const4386 QAbstractItemModel* Server::nickListModel() const
4387 {
4388     return m_nickListModel;
4389 }
4390 
startNickInfoChangedTimer()4391 void Server::startNickInfoChangedTimer()
4392 {
4393     if(!m_nickInfoChangedTimer->isActive())
4394         m_nickInfoChangedTimer->start();
4395 }
4396 
sendNickInfoChangedSignals()4397 void Server::sendNickInfoChangedSignals()
4398 {
4399     Q_EMIT nickInfoChanged();
4400 
4401     for (NickInfoPtr nickInfo : qAsConst(m_allNicks)) {
4402         if(nickInfo->isChanged())
4403         {
4404             Q_EMIT nickInfoChanged(this, nickInfo);
4405             nickInfo->setChanged(false);
4406         }
4407     }
4408 }
4409 
startChannelNickChangedTimer(const QString & channel)4410 void Server::startChannelNickChangedTimer(const QString& channel)
4411 {
4412     if(!m_channelNickChangedTimer->isActive())
4413         m_channelNickChangedTimer->start();
4414 
4415     m_changedChannels.append(channel);
4416 }
4417 
sendChannelNickChangedSignals()4418 void Server::sendChannelNickChangedSignals()
4419 {
4420     for (const QString& channel : qAsConst(m_changedChannels)) {
4421         if (m_joinedChannels.contains (channel))
4422         {
4423             Q_EMIT channelNickChanged(channel);
4424 
4425             for (ChannelNickPtr nick : qAsConst(*m_joinedChannels[channel])) {
4426                 if(nick->isChanged())
4427                 {
4428                     nick->setChanged(false);
4429                 }
4430             }
4431         }
4432     }
4433 
4434     m_changedChannels.clear();
4435 }
4436 
involuntaryQuit()4437 void Server::involuntaryQuit()
4438 {
4439     if((m_connectionState == Konversation::SSConnected || m_connectionState == Konversation::SSConnecting) &&
4440        (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6)))
4441     {
4442         quitServer();
4443         updateConnectionState(Konversation::SSInvoluntarilyDisconnected);
4444     }
4445 }
4446 
reconnectInvoluntary()4447 void Server::reconnectInvoluntary()
4448 {
4449     if(m_connectionState == Konversation::SSInvoluntarilyDisconnected)
4450         reconnectServer();
4451 }
4452 
initCapablityNames()4453 void Server::initCapablityNames()
4454 {
4455     m_capabilityNames = {
4456         { QStringLiteral("away-notify"),            AwayNotify },
4457         { QStringLiteral("extended-join"),          ExtendedJoin },
4458         { QStringLiteral("server-time"),            ServerTime },
4459         { QStringLiteral("znc.in/server-time-iso"), ServerTime },
4460         { QStringLiteral("userhost-in-names"),      UserHostInNames },
4461         { QStringLiteral("sasl"),                   SASL },
4462         { QStringLiteral("multi-prefix"),           MultiPrefix },
4463         { QStringLiteral("account-notify"),         AccountNotify },
4464         { QStringLiteral("znc.in/self-message"),    SelfMessage },
4465         { QStringLiteral("chghost"),                ChgHost },
4466         { QStringLiteral("cap-notify"),             CapNofify },
4467     };
4468 }
4469 
4470 
4471 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on;
4472 // vim: set et sw=4 ts=4 cino=l1,cs,U1:
4473