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 ¶meter, 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 ¶meter)
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