Add command queue prepend, prioritize PING/PONG
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QHostInfo>
22
23 #include "corenetwork.h"
24
25 #include "core.h"
26 #include "coreidentity.h"
27 #include "corenetworkconfig.h"
28 #include "coresession.h"
29 #include "coreuserinputhandler.h"
30 #include "networkevent.h"
31
32 // IRCv3 capabilities
33 #include "irccap.h"
34
35 INIT_SYNCABLE_OBJECT(CoreNetwork)
36 CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
37     : Network(networkid, session),
38     _coreSession(session),
39     _userInputHandler(new CoreUserInputHandler(this)),
40     _autoReconnectCount(0),
41     _quitRequested(false),
42
43     _previousConnectionAttemptFailed(false),
44     _lastUsedServerIndex(0),
45
46     _lastPingTime(0),
47     _pingCount(0),
48     _sendPings(false),
49     _requestedUserModes('-')
50 {
51     _autoReconnectTimer.setSingleShot(true);
52     connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
53
54     setPingInterval(networkConfig()->pingInterval());
55     connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
56
57     setAutoWhoDelay(networkConfig()->autoWhoDelay());
58     setAutoWhoInterval(networkConfig()->autoWhoInterval());
59
60     QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
61     foreach(QString chan, channels.keys()) {
62         _channelKeys[chan.toLower()] = channels[chan];
63     }
64
65     connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
66     connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
67     connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
68     connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
69     connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
70
71     connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
72     connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
73     connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
74     connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
75
76     connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
77     connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
78     connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
79     connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
80 #ifdef HAVE_SSL
81     connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
82     connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
83 #endif
84     connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
85
86     // IRCv3 capability handling
87     // These react to CAP messages from the server
88     connect(this, SIGNAL(capAdded(QString)), this, SLOT(serverCapAdded(QString)));
89     connect(this, SIGNAL(capAcknowledged(QString)), this, SLOT(serverCapAcknowledged(QString)));
90     connect(this, SIGNAL(capRemoved(QString)), this, SLOT(serverCapRemoved(QString)));
91
92     if (Quassel::isOptionSet("oidentd")) {
93         connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
94         connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
95     }
96 }
97
98
99 CoreNetwork::~CoreNetwork()
100 {
101     if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
102         disconnectFromIrc(false);  // clean up, but this does not count as requested disconnect!
103     disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
104     delete _userInputHandler;
105 }
106
107
108 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
109 {
110     if (!bufferName.isEmpty()) {
111         IrcChannel *channel = ircChannel(bufferName);
112         if (channel)
113             return channel->decodeString(string);
114     }
115     return decodeString(string);
116 }
117
118
119 QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
120 {
121     IrcUser *user = ircUser(userNick);
122     if (user)
123         return user->decodeString(string);
124     return decodeString(string);
125 }
126
127
128 QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
129 {
130     if (!bufferName.isEmpty()) {
131         IrcChannel *channel = ircChannel(bufferName);
132         if (channel)
133             return channel->encodeString(string);
134     }
135     return encodeString(string);
136 }
137
138
139 QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
140 {
141     IrcUser *user = ircUser(userNick);
142     if (user)
143         return user->encodeString(string);
144     return encodeString(string);
145 }
146
147
148 void CoreNetwork::connectToIrc(bool reconnecting)
149 {
150     if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
151         _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
152         if (unlimitedReconnectRetries())
153             _autoReconnectCount = -1;
154         else
155             _autoReconnectCount = autoReconnectRetries();
156     }
157     if (serverList().isEmpty()) {
158         qWarning() << "Server list empty, ignoring connect request!";
159         return;
160     }
161     CoreIdentity *identity = identityPtr();
162     if (!identity) {
163         qWarning() << "Invalid identity configures, ignoring connect request!";
164         return;
165     }
166
167     // cleaning up old quit reason
168     _quitReason.clear();
169
170     // Reset capability negotiation tracking, also handling server changes during reconnect
171     _capsQueued.clear();
172     clearCaps();
173     _capNegotiationActive = false;
174     _capInitialNegotiationEnded = false;
175
176     // use a random server?
177     if (useRandomServer()) {
178         _lastUsedServerIndex = qrand() % serverList().size();
179     }
180     else if (_previousConnectionAttemptFailed) {
181         // cycle to next server if previous connection attempt failed
182         displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
183         if (++_lastUsedServerIndex >= serverList().size()) {
184             _lastUsedServerIndex = 0;
185         }
186     }
187     _previousConnectionAttemptFailed = false;
188
189     Server server = usedServer();
190     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
191     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
192
193     if (server.useProxy) {
194         QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
195         socket.setProxy(proxy);
196     }
197     else {
198         socket.setProxy(QNetworkProxy::NoProxy);
199     }
200
201     enablePingTimeout();
202
203     // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
204     // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
205     QHostInfo::fromName(server.host);
206
207 #ifdef HAVE_SSL
208     if (server.useSsl) {
209         CoreIdentity *identity = identityPtr();
210         if (identity) {
211             socket.setLocalCertificate(identity->sslCert());
212             socket.setPrivateKey(identity->sslKey());
213         }
214         socket.connectToHostEncrypted(server.host, server.port);
215     }
216     else {
217         socket.connectToHost(server.host, server.port);
218     }
219 #else
220     socket.connectToHost(server.host, server.port);
221 #endif
222 }
223
224
225 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
226 {
227     _quitRequested = requested; // see socketDisconnected();
228     if (!withReconnect) {
229         _autoReconnectTimer.stop();
230         _autoReconnectCount = 0; // prohibiting auto reconnect
231     }
232     disablePingTimeout();
233     _msgQueue.clear();
234
235     IrcUser *me_ = me();
236     if (me_) {
237         QString awayMsg;
238         if (me_->isAway())
239             awayMsg = me_->awayMessage();
240         Core::setAwayMessage(userId(), networkId(), awayMsg);
241     }
242
243     if (reason.isEmpty() && identityPtr())
244         _quitReason = identityPtr()->quitReason();
245     else
246         _quitReason = reason;
247
248     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
249     if (socket.state() == QAbstractSocket::UnconnectedState) {
250         socketDisconnected();
251     } else {
252         if (socket.state() == QAbstractSocket::ConnectedState) {
253             userInputHandler()->issueQuit(_quitReason);
254         } else {
255             socket.close();
256         }
257         if (requested || withReconnect) {
258             // the irc server has 10 seconds to close the socket
259             _socketCloseTimer.start(10000);
260         }
261     }
262 }
263
264
265 void CoreNetwork::userInput(BufferInfo buf, QString msg)
266 {
267     userInputHandler()->handleUserInput(buf, msg);
268 }
269
270
271 void CoreNetwork::putRawLine(const QByteArray s, const bool prepend)
272 {
273     if (_tokenBucket > 0) {
274         writeToSocket(s);
275     } else {
276         if (prepend) {
277             _msgQueue.prepend(s);
278         } else {
279             _msgQueue.append(s);
280         }
281     }
282 }
283
284
285 void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix, const bool prepend)
286 {
287     QByteArray msg;
288
289     if (!prefix.isEmpty())
290         msg += ":" + prefix + " ";
291     msg += cmd.toUpper().toLatin1();
292
293     for (int i = 0; i < params.size(); i++) {
294         msg += " ";
295
296         if (i == params.size() - 1 && (params[i].contains(' ') || (!params[i].isEmpty() && params[i][0] == ':')))
297             msg += ":";
298
299         msg += params[i];
300     }
301
302     putRawLine(msg, prepend);
303 }
304
305
306 void CoreNetwork::putCmd(const QString &cmd, const QList<QList<QByteArray>> &params, const QByteArray &prefix, const bool prependAll)
307 {
308     QListIterator<QList<QByteArray>> i(params);
309     while (i.hasNext()) {
310         QList<QByteArray> msg = i.next();
311         putCmd(cmd, msg, prefix, prependAll);
312     }
313 }
314
315
316 void CoreNetwork::setChannelJoined(const QString &channel)
317 {
318     queueAutoWhoOneshot(channel); // check this new channel first
319
320     Core::setChannelPersistent(userId(), networkId(), channel, true);
321     Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
322 }
323
324
325 void CoreNetwork::setChannelParted(const QString &channel)
326 {
327     removeChannelKey(channel);
328     _autoWhoQueue.removeAll(channel.toLower());
329     _autoWhoPending.remove(channel.toLower());
330
331     Core::setChannelPersistent(userId(), networkId(), channel, false);
332 }
333
334
335 void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
336 {
337     if (key.isEmpty()) {
338         removeChannelKey(channel);
339     }
340     else {
341         _channelKeys[channel.toLower()] = key;
342     }
343 }
344
345
346 void CoreNetwork::removeChannelKey(const QString &channel)
347 {
348     _channelKeys.remove(channel.toLower());
349 }
350
351
352 #ifdef HAVE_QCA2
353 Cipher *CoreNetwork::cipher(const QString &target)
354 {
355     if (target.isEmpty())
356         return 0;
357
358     if (!Cipher::neededFeaturesAvailable())
359         return 0;
360
361     CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
362     if (channel) {
363         return channel->cipher();
364     }
365     CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
366     if (user) {
367         return user->cipher();
368     } else if (!isChannelName(target)) {
369         return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
370     }
371     return 0;
372 }
373
374
375 QByteArray CoreNetwork::cipherKey(const QString &target) const
376 {
377     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
378     if (c)
379         return c->cipher()->key();
380
381     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
382     if (u)
383         return u->cipher()->key();
384
385     return QByteArray();
386 }
387
388
389 void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
390 {
391     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
392     if (c) {
393         c->setEncrypted(c->cipher()->setKey(key));
394         return;
395     }
396
397     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
398     if (!u && !isChannelName(target))
399         u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
400
401     if (u) {
402         u->setEncrypted(u->cipher()->setKey(key));
403         return;
404     }
405 }
406
407
408 bool CoreNetwork::cipherUsesCBC(const QString &target)
409 {
410     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
411     if (c)
412         return c->cipher()->usesCBC();
413     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
414     if (u)
415         return u->cipher()->usesCBC();
416
417     return false;
418 }
419 #endif /* HAVE_QCA2 */
420
421 bool CoreNetwork::setAutoWhoDone(const QString &channel)
422 {
423     QString chan = channel.toLower();
424     if (_autoWhoPending.value(chan, 0) <= 0)
425         return false;
426     if (--_autoWhoPending[chan] <= 0)
427         _autoWhoPending.remove(chan);
428     return true;
429 }
430
431
432 void CoreNetwork::setMyNick(const QString &mynick)
433 {
434     Network::setMyNick(mynick);
435     if (connectionState() == Network::Initializing)
436         networkInitialized();
437 }
438
439
440 void CoreNetwork::socketHasData()
441 {
442     while (socket.canReadLine()) {
443         QByteArray s = socket.readLine();
444         if (s.endsWith("\r\n"))
445             s.chop(2);
446         else if (s.endsWith("\n"))
447             s.chop(1);
448         NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
449         event->setTimestamp(QDateTime::currentDateTimeUtc());
450         emit newEvent(event);
451     }
452 }
453
454
455 void CoreNetwork::socketError(QAbstractSocket::SocketError error)
456 {
457     if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
458         return;
459
460     _previousConnectionAttemptFailed = true;
461     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
462     emit connectionError(socket.errorString());
463     displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
464     emitConnectionError(socket.errorString());
465     if (socket.state() < QAbstractSocket::ConnectedState) {
466         socketDisconnected();
467     }
468 }
469
470
471 void CoreNetwork::socketInitialized()
472 {
473     CoreIdentity *identity = identityPtr();
474     if (!identity) {
475         qCritical() << "Identity invalid!";
476         disconnectFromIrc();
477         return;
478     }
479
480     Server server = usedServer();
481
482 #ifdef HAVE_SSL
483     // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
484     // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
485     if (!server.useSsl || !socket.isEncrypted()) {
486         emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
487     }
488
489     if (server.useSsl && !socket.isEncrypted()) {
490         // We'll finish setup once we're encrypted, and called again
491         return;
492     }
493 #else
494     emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
495 #endif
496
497     socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
498
499     // TokenBucket to avoid sending too much at once
500     _messageDelay = 2200;  // this seems to be a safe value (2.2 seconds delay)
501     _burstSize = 5;
502     _tokenBucket = _burstSize; // init with a full bucket
503     _tokenBucketTimer.start(_messageDelay);
504
505     // Request capabilities as per IRCv3.2 specifications
506     // Older servers should ignore this; newer servers won't downgrade to RFC1459
507     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Requesting capability list..."));
508     putRawLine(serverEncode(QString("CAP LS 302")));
509
510     if (!server.password.isEmpty()) {
511         putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
512     }
513     QString nick;
514     if (identity->nicks().isEmpty()) {
515         nick = "quassel";
516         qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
517     }
518     else {
519         nick = identity->nicks()[0];
520     }
521     putRawLine(serverEncode(QString("NICK %1").arg(nick)));
522     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
523 }
524
525
526 void CoreNetwork::socketDisconnected()
527 {
528     disablePingTimeout();
529     _msgQueue.clear();
530
531     _autoWhoCycleTimer.stop();
532     _autoWhoTimer.stop();
533     _autoWhoQueue.clear();
534     _autoWhoPending.clear();
535
536     _socketCloseTimer.stop();
537
538     _tokenBucketTimer.stop();
539
540     IrcUser *me_ = me();
541     if (me_) {
542         foreach(QString channel, me_->channels())
543         displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
544     }
545
546     setConnected(false);
547     emit disconnected(networkId());
548     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
549     if (_quitRequested) {
550         _quitRequested = false;
551         setConnectionState(Network::Disconnected);
552         Core::setNetworkConnected(userId(), networkId(), false);
553     }
554     else if (_autoReconnectCount != 0) {
555         setConnectionState(Network::Reconnecting);
556         if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
557             doAutoReconnect();  // first try is immediate
558         else
559             _autoReconnectTimer.start();
560     }
561 }
562
563
564 void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
565 {
566     Network::ConnectionState state;
567     switch (socketState) {
568     case QAbstractSocket::UnconnectedState:
569         state = Network::Disconnected;
570         socketDisconnected();
571         break;
572     case QAbstractSocket::HostLookupState:
573     case QAbstractSocket::ConnectingState:
574         state = Network::Connecting;
575         break;
576     case QAbstractSocket::ConnectedState:
577         state = Network::Initializing;
578         break;
579     case QAbstractSocket::ClosingState:
580         state = Network::Disconnecting;
581         break;
582     default:
583         state = Network::Disconnected;
584     }
585     setConnectionState(state);
586 }
587
588
589 void CoreNetwork::networkInitialized()
590 {
591     setConnectionState(Network::Initialized);
592     setConnected(true);
593     _quitRequested = false;
594
595     if (useAutoReconnect()) {
596         // reset counter
597         _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
598     }
599
600     // restore away state
601     QString awayMsg = Core::awayMessage(userId(), networkId());
602     if (!awayMsg.isEmpty())
603         userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId()));
604
605     sendPerform();
606
607     _sendPings = true;
608
609     if (networkConfig()->autoWhoEnabled()) {
610         _autoWhoCycleTimer.start();
611         _autoWhoTimer.start();
612         startAutoWhoCycle(); // FIXME wait for autojoin to be completed
613     }
614
615     Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
616     Core::setNetworkConnected(userId(), networkId(), true);
617 }
618
619
620 void CoreNetwork::sendPerform()
621 {
622     BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
623
624     // do auto identify
625     if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
626         userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
627     }
628
629     // restore old user modes if server default mode is set.
630     IrcUser *me_ = me();
631     if (me_) {
632         if (!me_->userModes().isEmpty()) {
633             restoreUserModes();
634         }
635         else {
636             connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
637             connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
638         }
639     }
640
641     // send perform list
642     foreach(QString line, perform()) {
643         if (!line.isEmpty()) userInput(statusBuf, line);
644     }
645
646     // rejoin channels we've been in
647     if (rejoinChannels()) {
648         QStringList channels, keys;
649         foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
650             QString key = channelKey(chan);
651             if (!key.isEmpty()) {
652                 channels.prepend(chan);
653                 keys.prepend(key);
654             }
655             else {
656                 channels.append(chan);
657             }
658         }
659         QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
660         if (!joinString.isEmpty())
661             userInputHandler()->handleJoin(statusBuf, joinString);
662     }
663 }
664
665
666 void CoreNetwork::restoreUserModes()
667 {
668     IrcUser *me_ = me();
669     Q_ASSERT(me_);
670
671     disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
672     disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
673
674     QString modesDelta = Core::userModes(userId(), networkId());
675     QString currentModes = me_->userModes();
676
677     QString addModes, removeModes;
678     if (modesDelta.contains('-')) {
679         addModes = modesDelta.section('-', 0, 0);
680         removeModes = modesDelta.section('-', 1);
681     }
682     else {
683         addModes = modesDelta;
684     }
685
686     addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
687     if (currentModes.isEmpty())
688         removeModes = QString();
689     else
690         removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
691
692     if (addModes.isEmpty() && removeModes.isEmpty())
693         return;
694
695     if (!addModes.isEmpty())
696         addModes = '+' + addModes;
697     if (!removeModes.isEmpty())
698         removeModes = '-' + removeModes;
699
700     // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
701     putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
702 }
703
704
705 void CoreNetwork::updateIssuedModes(const QString &requestedModes)
706 {
707     QString addModes;
708     QString removeModes;
709     bool addMode = true;
710
711     for (int i = 0; i < requestedModes.length(); i++) {
712         if (requestedModes[i] == '+') {
713             addMode = true;
714             continue;
715         }
716         if (requestedModes[i] == '-') {
717             addMode = false;
718             continue;
719         }
720         if (addMode) {
721             addModes += requestedModes[i];
722         }
723         else {
724             removeModes += requestedModes[i];
725         }
726     }
727
728     QString addModesOld = _requestedUserModes.section('-', 0, 0);
729     QString removeModesOld = _requestedUserModes.section('-', 1);
730
731     addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate
732     addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update
733     addModes += addModesOld;
734
735     removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate
736     removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update
737     removeModes += removeModesOld;
738
739     _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
740 }
741
742
743 void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
744 {
745     QString persistentUserModes = Core::userModes(userId(), networkId());
746
747     QString requestedAdd = _requestedUserModes.section('-', 0, 0);
748     QString requestedRemove = _requestedUserModes.section('-', 1);
749
750     QString persistentAdd, persistentRemove;
751     if (persistentUserModes.contains('-')) {
752         persistentAdd = persistentUserModes.section('-', 0, 0);
753         persistentRemove = persistentUserModes.section('-', 1);
754     }
755     else {
756         persistentAdd = persistentUserModes;
757     }
758
759     // remove modes we didn't issue
760     if (requestedAdd.isEmpty())
761         addModes = QString();
762     else
763         addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
764
765     if (requestedRemove.isEmpty())
766         removeModes = QString();
767     else
768         removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
769
770     // deduplicate
771     persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
772     persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
773
774     // update
775     persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
776     persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
777
778     // update issued mode list
779     requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
780     requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
781     _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
782
783     persistentAdd += addModes;
784     persistentRemove += removeModes;
785     Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
786 }
787
788
789 void CoreNetwork::resetPersistentModes()
790 {
791     _requestedUserModes = QString('-');
792     Core::setUserModes(userId(), networkId(), QString());
793 }
794
795
796 void CoreNetwork::setUseAutoReconnect(bool use)
797 {
798     Network::setUseAutoReconnect(use);
799     if (!use)
800         _autoReconnectTimer.stop();
801 }
802
803
804 void CoreNetwork::setAutoReconnectInterval(quint32 interval)
805 {
806     Network::setAutoReconnectInterval(interval);
807     _autoReconnectTimer.setInterval(interval * 1000);
808 }
809
810
811 void CoreNetwork::setAutoReconnectRetries(quint16 retries)
812 {
813     Network::setAutoReconnectRetries(retries);
814     if (_autoReconnectCount != 0) {
815         if (unlimitedReconnectRetries())
816             _autoReconnectCount = -1;
817         else
818             _autoReconnectCount = autoReconnectRetries();
819     }
820 }
821
822
823 void CoreNetwork::doAutoReconnect()
824 {
825     if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
826         qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
827         return;
828     }
829     if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
830         _autoReconnectCount--;  // -2 means we delay the next reconnect
831     connectToIrc(true);
832 }
833
834
835 void CoreNetwork::sendPing()
836 {
837     uint now = QDateTime::currentDateTime().toTime_t();
838     if (_pingCount != 0) {
839         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
840                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
841     }
842     if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
843         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
844         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
845         // and unable to even handle a ping answer. So we ignore those misses.
846         disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
847     }
848     else {
849         _lastPingTime = now;
850         _pingCount++;
851         // Don't send pings until the network is initialized
852         if(_sendPings)
853             userInputHandler()->handlePing(BufferInfo(), QString());
854     }
855 }
856
857
858 void CoreNetwork::enablePingTimeout(bool enable)
859 {
860     if (!enable)
861         disablePingTimeout();
862     else {
863         resetPingTimeout();
864         if (networkConfig()->pingTimeoutEnabled())
865             _pingTimer.start();
866     }
867 }
868
869
870 void CoreNetwork::disablePingTimeout()
871 {
872     _pingTimer.stop();
873     _sendPings = false;
874     resetPingTimeout();
875 }
876
877
878 void CoreNetwork::setPingInterval(int interval)
879 {
880     _pingTimer.setInterval(interval * 1000);
881 }
882
883 /******** IRCv3 Capability Negotiation ********/
884
885 void CoreNetwork::serverCapAdded(const QString &capability)
886 {
887     // Check if it's a known capability; if so, add it to the list
888     // Handle special cases first
889     if (capability == IrcCap::SASL) {
890         // Only request SASL if it's enabled
891         if (networkInfo().useSasl)
892             queueCap(capability);
893     } else if (IrcCap::knownCaps.contains(capability)) {
894         // Handling for general known capabilities
895         queueCap(capability);
896     }
897 }
898
899 void CoreNetwork::serverCapAcknowledged(const QString &capability)
900 {
901     // This may be called multiple times in certain situations.
902
903     // Handle core-side configuration
904     if (capability == IrcCap::AWAY_NOTIFY) {
905         // away-notify enabled, stop the autoWho timers, handle manually
906         setAutoWhoEnabled(false);
907     }
908
909     // Handle capabilities that require further messages sent to the IRC server
910     // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages
911     if (capability == IrcCap::SASL) {
912         // If SASL mechanisms specified, limit to what's accepted for authentication
913         // if the current identity has a cert set, use SASL EXTERNAL
914         // FIXME use event
915 #ifdef HAVE_SSL
916         if (!identityPtr()->sslCert().isNull()) {
917             if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::EXTERNAL)) {
918                 // EXTERNAL authentication supported, send request
919                 putRawLine(serverEncode("AUTHENTICATE EXTERNAL"));
920             } else {
921                 displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
922                            tr("SASL EXTERNAL authentication not supported"));
923                 sendNextCap();
924             }
925         } else {
926 #endif
927             if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::PLAIN)) {
928                 // PLAIN authentication supported, send request
929                 // Only working with PLAIN atm, blowfish later
930                 putRawLine(serverEncode("AUTHENTICATE PLAIN"));
931             } else {
932                 displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
933                            tr("SASL PLAIN authentication not supported"));
934                 sendNextCap();
935             }
936 #ifdef HAVE_SSL
937         }
938 #endif
939     }
940 }
941
942 void CoreNetwork::serverCapRemoved(const QString &capability)
943 {
944     // This may be called multiple times in certain situations.
945
946     // Handle special cases here
947     if (capability == IrcCap::AWAY_NOTIFY) {
948         // away-notify disabled, enable autoWho according to configuration
949         setAutoWhoEnabled(networkConfig()->autoWhoEnabled());
950     }
951 }
952
953 void CoreNetwork::queueCap(const QString &capability)
954 {
955     // IRCv3 specs all use lowercase capability names
956     QString _capLowercase = capability.toLower();
957     if (!_capsQueued.contains(_capLowercase)) {
958         _capsQueued.append(_capLowercase);
959     }
960 }
961
962 QString CoreNetwork::takeQueuedCap()
963 {
964     if (!_capsQueued.empty()) {
965         return _capsQueued.takeFirst();
966     } else {
967         return QString();
968     }
969 }
970
971 void CoreNetwork::beginCapNegotiation()
972 {
973     // Don't begin negotiation if no capabilities are queued to request
974     if (!capNegotiationInProgress())
975         return;
976
977     _capNegotiationActive = true;
978     displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
979                tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
980     displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
981                tr("Negotiating capabilities (requesting: %1)...").arg(_capsQueued.join(", ")));
982     sendNextCap();
983 }
984
985 void CoreNetwork::sendNextCap()
986 {
987     if (capNegotiationInProgress()) {
988         // Request the next capability and remove it from the list
989         // Handle one at a time so one capability failing won't NAK all of 'em
990         putRawLine(serverEncode(QString("CAP REQ :%1").arg(takeQueuedCap())));
991     } else {
992         // No pending desired capabilities, capability negotiation finished
993         // If SASL requested but not available, print a warning
994         if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
995             displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
996                        tr("SASL authentication currently not supported by server"));
997
998         if (_capNegotiationActive) {
999             displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
1000                    tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
1001             _capNegotiationActive = false;
1002         }
1003
1004         // If nick registration is already complete, CAP END is not required
1005         if (!_capInitialNegotiationEnded) {
1006             putRawLine(serverEncode(QString("CAP END")));
1007             _capInitialNegotiationEnded = true;
1008         }
1009     }
1010 }
1011
1012 /******** AutoWHO ********/
1013
1014 void CoreNetwork::startAutoWhoCycle()
1015 {
1016     if (!_autoWhoQueue.isEmpty()) {
1017         _autoWhoCycleTimer.stop();
1018         return;
1019     }
1020     _autoWhoQueue = channels();
1021 }
1022
1023 void CoreNetwork::queueAutoWhoOneshot(const QString &channelOrNick)
1024 {
1025     // Prepend so these new channels/nicks are the first to be checked
1026     // Don't allow duplicates
1027     if (!_autoWhoQueue.contains(channelOrNick.toLower())) {
1028         _autoWhoQueue.prepend(channelOrNick.toLower());
1029     }
1030     if (capEnabled(IrcCap::AWAY_NOTIFY)) {
1031         // When away-notify is active, the timer's stopped.  Start a new cycle to who this channel.
1032         setAutoWhoEnabled(true);
1033     }
1034 }
1035
1036
1037 void CoreNetwork::setAutoWhoDelay(int delay)
1038 {
1039     _autoWhoTimer.setInterval(delay * 1000);
1040 }
1041
1042
1043 void CoreNetwork::setAutoWhoInterval(int interval)
1044 {
1045     _autoWhoCycleTimer.setInterval(interval * 1000);
1046 }
1047
1048
1049 void CoreNetwork::setAutoWhoEnabled(bool enabled)
1050 {
1051     if (enabled && isConnected() && !_autoWhoTimer.isActive())
1052         _autoWhoTimer.start();
1053     else if (!enabled) {
1054         _autoWhoTimer.stop();
1055         _autoWhoCycleTimer.stop();
1056     }
1057 }
1058
1059
1060 void CoreNetwork::sendAutoWho()
1061 {
1062     // Don't send autowho if there are still some pending
1063     if (_autoWhoPending.count())
1064         return;
1065
1066     while (!_autoWhoQueue.isEmpty()) {
1067         QString chanOrNick = _autoWhoQueue.takeFirst();
1068         // Check if it's a known channel or nick
1069         IrcChannel *ircchan = ircChannel(chanOrNick);
1070         IrcUser *ircuser = ircUser(chanOrNick);
1071         if (ircchan) {
1072             // Apply channel limiting rules
1073             // If using away-notify, don't impose channel size limits in order to capture away
1074             // state of everyone.  Auto-who won't run on a timer so network impact is minimal.
1075             if (networkConfig()->autoWhoNickLimit() > 0
1076                 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit()
1077                 && !capEnabled(IrcCap::AWAY_NOTIFY))
1078                 continue;
1079             _autoWhoPending[chanOrNick.toLower()]++;
1080         } else if (ircuser) {
1081             // Checking a nick, add it to the pending list
1082             _autoWhoPending[ircuser->nick().toLower()]++;
1083         } else {
1084             // Not a channel or a nick, skip it
1085             qDebug() << "Skipping who polling of unknown channel or nick" << chanOrNick;
1086             continue;
1087         }
1088         if (supports("WHOX")) {
1089             // Use WHO extended to poll away users and/or user accounts
1090             // See http://faerion.sourceforge.net/doc/irc/whox.var
1091             // And https://github.com/hexchat/hexchat/blob/c874a9525c9b66f1d5ddcf6c4107d046eba7e2c5/src/common/proto-irc.c#L750
1092             putRawLine(serverEncode(QString("WHO %1 %%chtsunfra,%2")
1093                                     .arg(serverEncode(chanOrNick), QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))));
1094         } else {
1095             putRawLine(serverEncode(QString("WHO %1").arg(chanOrNick)));
1096         }
1097         break;
1098     }
1099
1100     if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()
1101         && !capEnabled(IrcCap::AWAY_NOTIFY)) {
1102         // Timer was stopped, means a new cycle is due immediately
1103         // Don't run a new cycle if using away-notify; server will notify as appropriate
1104         _autoWhoCycleTimer.start();
1105         startAutoWhoCycle();
1106     } else if (capEnabled(IrcCap::AWAY_NOTIFY) && _autoWhoCycleTimer.isActive()) {
1107         // Don't run another who cycle if away-notify is enabled
1108         _autoWhoCycleTimer.stop();
1109     }
1110 }
1111
1112
1113 #ifdef HAVE_SSL
1114 void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
1115 {
1116     Q_UNUSED(sslErrors)
1117     socket.ignoreSslErrors();
1118     // TODO errorhandling
1119 }
1120
1121
1122 #endif  // HAVE_SSL
1123
1124 void CoreNetwork::fillBucketAndProcessQueue()
1125 {
1126     if (_tokenBucket < _burstSize) {
1127         _tokenBucket++;
1128     }
1129
1130     while (_msgQueue.size() > 0 && _tokenBucket > 0) {
1131         writeToSocket(_msgQueue.takeFirst());
1132     }
1133 }
1134
1135
1136 void CoreNetwork::writeToSocket(const QByteArray &data)
1137 {
1138     socket.write(data);
1139     socket.write("\r\n");
1140     _tokenBucket--;
1141 }
1142
1143
1144 Network::Server CoreNetwork::usedServer() const
1145 {
1146     if (_lastUsedServerIndex < serverList().count())
1147         return serverList()[_lastUsedServerIndex];
1148
1149     if (!serverList().isEmpty())
1150         return serverList()[0];
1151
1152     return Network::Server();
1153 }
1154
1155
1156 void CoreNetwork::requestConnect() const
1157 {
1158     if (connectionState() != Disconnected) {
1159         qWarning() << "Requesting connect while already being connected!";
1160         return;
1161     }
1162     QMetaObject::invokeMethod(const_cast<CoreNetwork *>(this), "connectToIrc", Qt::QueuedConnection);
1163 }
1164
1165
1166 void CoreNetwork::requestDisconnect() const
1167 {
1168     if (connectionState() == Disconnected) {
1169         qWarning() << "Requesting disconnect while not being connected!";
1170         return;
1171     }
1172     userInputHandler()->handleQuit(BufferInfo(), QString());
1173 }
1174
1175
1176 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info)
1177 {
1178     Network::Server currentServer = usedServer();
1179     setNetworkInfo(info);
1180     Core::updateNetwork(coreSession()->user(), info);
1181
1182     // the order of the servers might have changed,
1183     // so we try to find the previously used server
1184     _lastUsedServerIndex = 0;
1185     for (int i = 0; i < serverList().count(); i++) {
1186         Network::Server server = serverList()[i];
1187         if (server.host == currentServer.host && server.port == currentServer.port) {
1188             _lastUsedServerIndex = i;
1189             break;
1190         }
1191     }
1192 }
1193
1194
1195 QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, std::function<QList<QByteArray>(QString &)> cmdGenerator)
1196 {
1197     QString wrkMsg(message);
1198     QList<QList<QByteArray>> msgsToSend;
1199
1200     // do while (wrkMsg.size() > 0)
1201     do {
1202         // First, check to see if the whole message can be sent at once.  The
1203         // cmdGenerator function is passed in by the caller and is used to encode
1204         // and encrypt (if applicable) the message, since different callers might
1205         // want to use different encoding or encode different values.
1206         int splitPos = wrkMsg.size();
1207         QList<QByteArray> initialSplitMsgEnc = cmdGenerator(wrkMsg);
1208         int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc);
1209
1210         if (initialOverrun) {
1211             // If the message was too long to be sent, first try splitting it along
1212             // word boundaries with QTextBoundaryFinder.
1213             QString splitMsg(wrkMsg);
1214             QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg);
1215             qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1216             QList<QByteArray> splitMsgEnc;
1217             int overrun = initialOverrun;
1218
1219             while (overrun) {
1220                 splitPos = qtbf.toPreviousBoundary();
1221
1222                 // splitPos==-1 means the QTBF couldn't find a split point at all and
1223                 // splitPos==0 means the QTBF could only find a boundary at the beginning of
1224                 // the string.  Neither one of these works for us.
1225                 if (splitPos > 0) {
1226                     // If a split point could be found, split the message there, calculate the
1227                     // overrun, and continue with the loop.
1228                     splitMsg = splitMsg.left(splitPos);
1229                     splitMsgEnc = cmdGenerator(splitMsg);
1230                     overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc);
1231                 }
1232                 else {
1233                     // If a split point could not be found (the beginning of the message
1234                     // is reached without finding a split point short enough to send) and we
1235                     // are still in Word mode, switch to Grapheme mode.  We also need to restore
1236                     // the full wrkMsg to splitMsg, since splitMsg may have been cut down during
1237                     // the previous attempt to find a split point.
1238                     if (qtbf.type() == QTextBoundaryFinder::Word) {
1239                         splitMsg = wrkMsg;
1240                         splitPos = splitMsg.size();
1241                         QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg);
1242                         graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
1243                         qtbf = graphemeQtbf;
1244                     }
1245                     else {
1246                         // If the QTBF fails to find a split point in Grapheme mode, we give up.
1247                         // This should never happen, but it should be handled anyway.
1248                         qWarning() << "Unexpected failure to split message!";
1249                         return msgsToSend;
1250                     }
1251                 }
1252             }
1253
1254             // Once a message of sendable length has been found, remove it from the wrkMsg and
1255             // add it to the list of messages to be sent.
1256             wrkMsg.remove(0, splitPos);
1257             msgsToSend.append(splitMsgEnc);
1258         }
1259         else{
1260             // If the entire remaining message is short enough to be sent all at once, remove
1261             // it from the wrkMsg and add it to the list of messages to be sent.
1262             wrkMsg.remove(0, splitPos);
1263             msgsToSend.append(initialSplitMsgEnc);
1264         }
1265     } while (wrkMsg.size() > 0);
1266
1267     return msgsToSend;
1268 }