+ : Network(networkid, session),
+ _coreSession(session),
+ _userInputHandler(new CoreUserInputHandler(this)),
+ _autoReconnectCount(0),
+ _quitRequested(false),
+ _disconnectExpected(false),
+
+ _previousConnectionAttemptFailed(false),
+ _lastUsedServerIndex(0),
+
+ _requestedUserModes('-')
+{
+ // Check if raw IRC logging is enabled
+ _debugLogRawIrc = (Quassel::isOptionSet("debug-irc") || Quassel::isOptionSet("debug-irc-id"));
+ _debugLogRawNetId = Quassel::optionValue("debug-irc-id").toInt();
+
+ _autoReconnectTimer.setSingleShot(true);
+ connect(&_socketCloseTimer, &QTimer::timeout, this, &CoreNetwork::socketCloseTimeout);
+
+ setPingInterval(networkConfig()->pingInterval());
+ connect(&_pingTimer, &QTimer::timeout, this, &CoreNetwork::sendPing);
+
+ setAutoWhoDelay(networkConfig()->autoWhoDelay());
+ setAutoWhoInterval(networkConfig()->autoWhoInterval());
+
+ QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
+ foreach(QString chan, channels.keys()) {
+ _channelKeys[chan.toLower()] = channels[chan];
+ }
+
+ QHash<QString, QByteArray> bufferCiphers = coreSession()->bufferCiphers(networkId());
+ foreach(QString buffer, bufferCiphers.keys()) {
+ storeChannelCipherKey(buffer.toLower(), bufferCiphers[buffer]);
+ }
+
+ connect(networkConfig(), &NetworkConfig::pingTimeoutEnabledSet, this, &CoreNetwork::enablePingTimeout);
+ connect(networkConfig(), &NetworkConfig::pingIntervalSet, this, &CoreNetwork::setPingInterval);
+ connect(networkConfig(), &NetworkConfig::autoWhoEnabledSet, this, &CoreNetwork::setAutoWhoEnabled);
+ connect(networkConfig(), &NetworkConfig::autoWhoIntervalSet, this, &CoreNetwork::setAutoWhoInterval);
+ connect(networkConfig(), &NetworkConfig::autoWhoDelaySet, this, &CoreNetwork::setAutoWhoDelay);
+
+ connect(&_autoReconnectTimer, &QTimer::timeout, this, &CoreNetwork::doAutoReconnect);
+ connect(&_autoWhoTimer, &QTimer::timeout, this, &CoreNetwork::sendAutoWho);
+ connect(&_autoWhoCycleTimer, &QTimer::timeout, this, &CoreNetwork::startAutoWhoCycle);
+ connect(&_tokenBucketTimer, &QTimer::timeout, this, &CoreNetwork::checkTokenBucket);
+
+ connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
+ connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
+ connect(&socket, &QAbstractSocket::stateChanged, this, &CoreNetwork::socketStateChanged);
+ connect(&socket, &QIODevice::readyRead, this, &CoreNetwork::socketHasData);
+#ifdef HAVE_SSL
+ connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
+ connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+#endif
+ connect(this, &CoreNetwork::newEvent, coreSession()->eventManager(), &EventManager::postEvent);
+
+ // Custom rate limiting
+ // These react to the user changing settings in the client
+ connect(this, &Network::useCustomMessageRateSet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::messageRateBurstSizeSet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::messageRateDelaySet, this, &CoreNetwork::updateRateLimiting);
+ connect(this, &Network::unlimitedMessageRateSet, this, &CoreNetwork::updateRateLimiting);
+
+ // IRCv3 capability handling
+ // These react to CAP messages from the server
+ connect(this, &Network::capAdded, this, &CoreNetwork::serverCapAdded);
+ connect(this, &Network::capAcknowledged, this, &CoreNetwork::serverCapAcknowledged);
+ connect(this, &Network::capRemoved, this, &CoreNetwork::serverCapRemoved);
+
+ if (Quassel::isOptionSet("oidentd")) {
+ connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)),
+ Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)),
+ Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)));
+ }
+
+ if (Quassel::isOptionSet("ident-daemon")) {
+ connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)),
+ Core::instance()->identServer(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)),
+ Core::instance()->identServer(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16, qint64)));
+ }
+}
+
+
+CoreNetwork::~CoreNetwork()
+{
+ // Ensure we don't get any more signals from the socket while shutting down
+ disconnect(&socket, nullptr, this, nullptr);
+ if (!forceDisconnect()) {
+ qWarning() << QString{"Could not disconnect from network %1 (network ID: %2, user ID: %3)"}
+ .arg(networkName()).arg(networkId().toInt()).arg(userId().toInt());
+ }
+}
+
+
+bool CoreNetwork::forceDisconnect(int msecs)
+{
+ if (socket.state() == QAbstractSocket::UnconnectedState) {
+ // Socket already disconnected.
+ return true;
+ }
+ // Request a socket-level disconnect if not already happened
+ socket.disconnectFromHost();
+ if (socket.state() != QAbstractSocket::UnconnectedState) {
+ return socket.waitForDisconnected(msecs);
+ }
+ return true;
+}
+
+
+QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
+{
+ if (!bufferName.isEmpty()) {
+ IrcChannel *channel = ircChannel(bufferName);
+ if (channel)
+ return channel->decodeString(string);
+ }
+ return decodeString(string);
+}
+
+
+QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
+{
+ IrcUser *user = ircUser(userNick);
+ if (user)
+ return user->decodeString(string);
+ return decodeString(string);
+}
+
+
+QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
+{
+ if (!bufferName.isEmpty()) {
+ IrcChannel *channel = ircChannel(bufferName);
+ if (channel)
+ return channel->encodeString(string);
+ }
+ return encodeString(string);
+}
+
+
+QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
+{
+ IrcUser *user = ircUser(userNick);
+ if (user)
+ return user->encodeString(string);
+ return encodeString(string);
+}
+
+
+void CoreNetwork::connectToIrc(bool reconnecting)
+{
+ if (_shuttingDown) {
+ return;
+ }
+
+ if (Core::instance()->identServer()) {
+ _socketId = Core::instance()->identServer()->addWaitingSocket();
+ }
+
+ if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
+ _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
+ if (unlimitedReconnectRetries())
+ _autoReconnectCount = -1;
+ else
+ _autoReconnectCount = autoReconnectRetries();
+ }
+ if (serverList().isEmpty()) {
+ qWarning() << "Server list empty, ignoring connect request!";
+ return;
+ }
+ CoreIdentity *identity = identityPtr();
+ if (!identity) {
+ qWarning() << "Invalid identity configures, ignoring connect request!";
+ return;
+ }
+
+ // cleaning up old quit reason
+ _quitReason.clear();
+
+ // Reset capability negotiation tracking, also handling server changes during reconnect
+ _capsQueuedIndividual.clear();
+ _capsQueuedBundled.clear();
+ clearCaps();
+ _capNegotiationActive = false;
+ _capInitialNegotiationEnded = false;
+
+ // use a random server?
+ if (useRandomServer()) {
+ _lastUsedServerIndex = qrand() % serverList().size();
+ }
+ else if (_previousConnectionAttemptFailed) {
+ // cycle to next server if previous connection attempt failed
+ _previousConnectionAttemptFailed = false;
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
+ if (++_lastUsedServerIndex >= serverList().size()) {
+ _lastUsedServerIndex = 0;
+ }
+ }
+ else {
+ // Start out with the top server in the list
+ _lastUsedServerIndex = 0;
+ }
+
+ Server server = usedServer();
+ displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
+
+ if (server.useProxy) {
+ QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
+ socket.setProxy(proxy);
+ }
+ else {
+ socket.setProxy(QNetworkProxy::NoProxy);
+ }
+
+ enablePingTimeout();
+
+ // Reset tracking for valid timestamps in PONG replies
+ setPongTimestampValid(false);
+
+ // Qt caches DNS entries for a minute, resulting in round-robin (e.g. for chat.freenode.net) not working if several users
+ // connect at a similar time. QHostInfo::fromName(), however, always performs a fresh lookup, overwriting the cache entry.
+ if (! server.useProxy) {
+ //Avoid hostname lookups when a proxy is specified. The lookups won't use the proxy and may therefore leak the DNS
+ //hostname of the server. Qt's DNS cache also isn't used by the proxy so we don't need to refresh the entry.
+ QHostInfo::fromName(server.host);
+ }
+#ifdef HAVE_SSL
+ if (server.useSsl) {
+ CoreIdentity *identity = identityPtr();
+ if (identity) {
+ socket.setLocalCertificate(identity->sslCert());
+ socket.setPrivateKey(identity->sslKey());
+ }
+ socket.connectToHostEncrypted(server.host, server.port);
+ }
+ else {
+ socket.connectToHost(server.host, server.port);
+ }
+#else
+ socket.connectToHost(server.host, server.port);
+#endif
+}
+
+
+void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
+{
+ // Disconnecting from the network, should expect a socket close or error
+ _disconnectExpected = true;
+ _quitRequested = requested; // see socketDisconnected();
+ if (!withReconnect) {
+ _autoReconnectTimer.stop();
+ _autoReconnectCount = 0; // prohibiting auto reconnect
+ }
+ disablePingTimeout();
+ _msgQueue.clear();
+
+ IrcUser *me_ = me();
+ if (me_) {
+ QString awayMsg;
+ if (me_->isAway())
+ awayMsg = me_->awayMessage();
+ Core::setAwayMessage(userId(), networkId(), awayMsg);
+ }
+
+ if (reason.isEmpty() && identityPtr())
+ _quitReason = identityPtr()->quitReason();
+ else
+ _quitReason = reason;
+
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
+ if (socket.state() == QAbstractSocket::UnconnectedState) {
+ socketDisconnected();
+ }
+ else {
+ if (socket.state() == QAbstractSocket::ConnectedState) {
+ // If shutting down, prioritize the QUIT command
+ userInputHandler()->issueQuit(_quitReason, _shuttingDown);
+ }
+ else {
+ socket.close();
+ }
+ if (socket.state() != QAbstractSocket::UnconnectedState) {
+ // Wait for up to 10 seconds for the socket to close cleanly, then it will be forcefully aborted
+ _socketCloseTimer.start(10000);
+ }
+ }
+}
+
+
+void CoreNetwork::socketCloseTimeout()
+{
+ qWarning() << QString{"Timed out quitting network %1 (network ID: %2, user ID: %3)"}
+ .arg(networkName()).arg(networkId().toInt()).arg(userId().toInt());
+ socket.abort();
+}
+
+
+void CoreNetwork::shutdown()
+{
+ _shuttingDown = true;
+ disconnectFromIrc(false, {}, false);
+}
+
+
+void CoreNetwork::userInput(BufferInfo buf, QString msg)
+{
+ userInputHandler()->handleUserInput(buf, msg);
+}
+
+
+void CoreNetwork::putRawLine(const QByteArray s, const bool prepend)
+{
+ if (_tokenBucket > 0 || (_skipMessageRates && _msgQueue.size() == 0)) {
+ // If there's tokens remaining, ...
+ // Or rate limits don't apply AND no messages are in queue (to prevent out-of-order), ...
+ // Send the message now.
+ writeToSocket(s);
+ } else {
+ // Otherwise, queue the message for later
+ if (prepend) {
+ // Jump to the start, skipping other messages
+ _msgQueue.prepend(s);
+ } else {
+ // Add to back, waiting in order
+ _msgQueue.append(s);
+ }
+ }
+}
+
+
+void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> ¶ms, const QByteArray &prefix, const bool prepend)
+{
+ QByteArray msg;
+
+ if (!prefix.isEmpty())
+ msg += ":" + prefix + " ";
+ msg += cmd.toUpper().toLatin1();
+
+ for (int i = 0; i < params.size(); i++) {
+ msg += " ";
+
+ if (i == params.size() - 1 && (params[i].contains(' ') || (!params[i].isEmpty() && params[i][0] == ':')))
+ msg += ":";
+
+ msg += params[i];
+ }
+
+ putRawLine(msg, prepend);
+}
+
+
+void CoreNetwork::putCmd(const QString &cmd, const QList<QList<QByteArray>> ¶ms, const QByteArray &prefix, const bool prependAll)
+{
+ QListIterator<QList<QByteArray>> i(params);
+ while (i.hasNext()) {
+ QList<QByteArray> msg = i.next();
+ putCmd(cmd, msg, prefix, prependAll);
+ }
+}
+
+
+void CoreNetwork::setChannelJoined(const QString &channel)
+{
+ queueAutoWhoOneshot(channel); // check this new channel first
+
+ Core::setChannelPersistent(userId(), networkId(), channel, true);
+ Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
+}
+
+
+void CoreNetwork::setChannelParted(const QString &channel)
+{
+ removeChannelKey(channel);
+ _autoWhoQueue.removeAll(channel.toLower());
+ _autoWhoPending.remove(channel.toLower());
+
+ Core::setChannelPersistent(userId(), networkId(), channel, false);
+}
+
+
+void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
+{
+ if (key.isEmpty()) {
+ removeChannelKey(channel);
+ }
+ else {
+ _channelKeys[channel.toLower()] = key;
+ }
+}
+
+
+void CoreNetwork::removeChannelKey(const QString &channel)
+{
+ _channelKeys.remove(channel.toLower());
+}
+
+
+#ifdef HAVE_QCA2
+Cipher *CoreNetwork::cipher(const QString &target)
+{
+ if (target.isEmpty())
+ return nullptr;
+
+ if (!Cipher::neededFeaturesAvailable())
+ return nullptr;
+
+ auto *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
+ if (channel) {
+ return channel->cipher();
+ }
+ auto *user = qobject_cast<CoreIrcUser *>(ircUser(target));
+ if (user) {
+ return user->cipher();
+ } else if (!isChannelName(target)) {
+ return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
+ }
+ return nullptr;
+}
+
+
+QByteArray CoreNetwork::cipherKey(const QString &target) const
+{
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ if (c)
+ return c->cipher()->key();
+
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ if (u)
+ return u->cipher()->key();
+
+ return QByteArray();
+}
+
+
+void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
+{
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ if (c) {
+ c->setEncrypted(c->cipher()->setKey(key));
+ coreSession()->setBufferCipher(networkId(), target, key);
+ return;
+ }
+
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ if (!u && !isChannelName(target))
+ u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
+
+ if (u) {
+ u->setEncrypted(u->cipher()->setKey(key));
+ coreSession()->setBufferCipher(networkId(), target, key);
+ return;
+ }
+}
+
+
+bool CoreNetwork::cipherUsesCBC(const QString &target)
+{
+ auto *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+ if (c)
+ return c->cipher()->usesCBC();
+ auto *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+ if (u)
+ return u->cipher()->usesCBC();
+
+ return false;
+}
+#endif /* HAVE_QCA2 */
+
+bool CoreNetwork::setAutoWhoDone(const QString &name)
+{
+ QString chanOrNick = name.toLower();
+ if (_autoWhoPending.value(chanOrNick, 0) <= 0)
+ return false;
+ if (--_autoWhoPending[chanOrNick] <= 0)
+ _autoWhoPending.remove(chanOrNick);
+ return true;
+}
+
+
+void CoreNetwork::setMyNick(const QString &mynick)
+{
+ Network::setMyNick(mynick);
+ if (connectionState() == Network::Initializing)
+ networkInitialized();
+}
+
+
+void CoreNetwork::socketHasData()
+{
+ while (socket.canReadLine()) {
+ QByteArray s = socket.readLine();
+ if (s.endsWith("\r\n"))
+ s.chop(2);
+ else if (s.endsWith("\n"))
+ s.chop(1);
+ NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
+ event->setTimestamp(QDateTime::currentDateTimeUtc());
+ emit newEvent(event);
+ }
+}
+
+
+void CoreNetwork::socketError(QAbstractSocket::SocketError error)
+{
+ // Ignore socket closed errors if expected
+ if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
+ return;
+ }
+
+ _previousConnectionAttemptFailed = true;
+ qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
+ emit connectionError(socket.errorString());
+ displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
+ emitConnectionError(socket.errorString());
+ if (socket.state() < QAbstractSocket::ConnectedState) {
+ socketDisconnected();
+ }
+}
+
+
+void CoreNetwork::socketInitialized()
+{
+ CoreIdentity *identity = identityPtr();
+ if (!identity) {
+ qCritical() << "Identity invalid!";
+ disconnectFromIrc();
+ return;
+ }
+
+ Server server = usedServer();
+
+#ifdef HAVE_SSL
+ // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
+ // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
+ if (!server.useSsl || !socket.isEncrypted()) {
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
+ }
+
+ if (server.useSsl && !socket.isEncrypted()) {
+ // We'll finish setup once we're encrypted, and called again
+ return;
+ }
+#else
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
+#endif
+
+ socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
+
+ // Update the TokenBucket, force-enabling unlimited message rates for initial registration and
+ // capability negotiation. networkInitialized() will call updateRateLimiting() without the
+ // force flag to apply user preferences. When making changes, ensure that this still happens!
+ // As Quassel waits for CAP ACK/NAK and AUTHENTICATE replies, this shouldn't ever fill the IRC
+ // server receive queue and cause a kill. "Shouldn't" being the operative word; the real world
+ // is a scary place.
+ updateRateLimiting(true);
+ // Fill up the token bucket as we're connecting from scratch
+ resetTokenBucket();
+
+ // Request capabilities as per IRCv3.2 specifications
+ // Older servers should ignore this; newer servers won't downgrade to RFC1459
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Requesting capability list..."));
+ putRawLine(serverEncode(QString("CAP LS 302")));
+
+ if (!server.password.isEmpty()) {
+ putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
+ }
+ QString nick;
+ if (identity->nicks().isEmpty()) {
+ nick = "quassel";
+ qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
+ }
+ else {
+ nick = identity->nicks()[0];
+ }
+ putRawLine(serverEncode(QString("NICK %1").arg(nick)));
+ // Only allow strict-compliant idents when strict mode is enabled
+ putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(
+ coreSession()->strictCompliantIdent(identity),
+ identity->realName())));
+}
+
+
+void CoreNetwork::socketDisconnected()
+{
+ disablePingTimeout();
+ _msgQueue.clear();
+
+ _autoWhoCycleTimer.stop();
+ _autoWhoTimer.stop();
+ _autoWhoQueue.clear();
+ _autoWhoPending.clear();
+
+ _socketCloseTimer.stop();
+
+ _tokenBucketTimer.stop();
+
+ IrcUser *me_ = me();
+ if (me_) {
+ foreach(QString channel, me_->channels())
+ displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
+ }
+
+ setConnected(false);
+ emit disconnected(networkId());
+ emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort(), _socketId);
+ // Reset disconnect expectations
+ _disconnectExpected = false;
+ if (_quitRequested) {
+ _quitRequested = false;
+ setConnectionState(Network::Disconnected);
+ Core::setNetworkConnected(userId(), networkId(), false);
+ }
+ else if (_autoReconnectCount != 0) {
+ setConnectionState(Network::Reconnecting);
+ if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
+ doAutoReconnect(); // first try is immediate
+ else
+ _autoReconnectTimer.start();
+ }
+}
+
+
+void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
+{
+ Network::ConnectionState state;
+ switch (socketState) {
+ case QAbstractSocket::UnconnectedState:
+ state = Network::Disconnected;
+ socketDisconnected();
+ break;
+ case QAbstractSocket::HostLookupState:
+ case QAbstractSocket::ConnectingState:
+ state = Network::Connecting;
+ break;
+ case QAbstractSocket::ConnectedState:
+ state = Network::Initializing;
+ break;
+ case QAbstractSocket::ClosingState:
+ state = Network::Disconnecting;
+ break;
+ default:
+ state = Network::Disconnected;
+ }
+ setConnectionState(state);
+}
+
+
+void CoreNetwork::networkInitialized()
+{
+ setConnectionState(Network::Initialized);
+ setConnected(true);
+ _disconnectExpected = false;
+ _quitRequested = false;
+
+ // Update the TokenBucket with specified rate-limiting settings, removing the force-unlimited
+ // flag used for initial registration and capability negotiation.
+ updateRateLimiting();
+
+ if (useAutoReconnect()) {
+ // reset counter
+ _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
+ }
+
+ // restore away state
+ QString awayMsg = Core::awayMessage(userId(), networkId());
+ if (!awayMsg.isEmpty()) {
+ // Don't re-apply any timestamp formatting in order to preserve escaped percent signs, e.g.
+ // '%%%%%%%%' -> '%%%%' If processed again, it'd result in '%%'.
+ userInputHandler()->handleAway(BufferInfo(), awayMsg, true);
+ }
+
+ sendPerform();
+
+ _sendPings = true;
+
+ if (networkConfig()->autoWhoEnabled()) {
+ _autoWhoCycleTimer.start();
+ _autoWhoTimer.start();
+ startAutoWhoCycle(); // FIXME wait for autojoin to be completed
+ }
+
+ Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
+ Core::setNetworkConnected(userId(), networkId(), true);
+}
+
+
+void CoreNetwork::sendPerform()
+{
+ BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
+
+ // do auto identify
+ if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
+ userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
+ }
+
+ // restore old user modes if server default mode is set.
+ IrcUser *me_ = me();
+ if (me_) {
+ if (!me_->userModes().isEmpty()) {
+ restoreUserModes();
+ }
+ else {
+ connect(me_, &IrcUser::userModesSet, this, &CoreNetwork::restoreUserModes);
+ connect(me_, &IrcUser::userModesAdded, this, &CoreNetwork::restoreUserModes);
+ }
+ }
+
+ // send perform list
+ foreach(QString line, perform()) {
+ if (!line.isEmpty()) userInput(statusBuf, line);
+ }
+
+ // rejoin channels we've been in
+ if (rejoinChannels()) {
+ QStringList channels, keys;
+ foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
+ QString key = channelKey(chan);
+ if (!key.isEmpty()) {
+ channels.prepend(chan);
+ keys.prepend(key);
+ }
+ else {
+ channels.append(chan);
+ }
+ }
+ QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
+ if (!joinString.isEmpty())
+ userInputHandler()->handleJoin(statusBuf, joinString);
+ }
+}
+
+
+void CoreNetwork::restoreUserModes()