Implement IRCv3.2 capability negotiation
[quassel.git] / src / core / corenetwork.cpp
index 942e32f..ecf2a2d 100644 (file)
@@ -81,7 +81,7 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
     connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
 
     if (Quassel::isOptionSet("oidentd")) {
-        connect(this, SIGNAL(socketOpen(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
+        connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
         connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
     }
 }
@@ -158,6 +158,11 @@ void CoreNetwork::connectToIrc(bool reconnecting)
     // cleaning up old quit reason
     _quitReason.clear();
 
+    // reset capability negotiation in case server changes during a reconnect
+    _capsQueued.clear();
+    _capsPending.clear();
+    _capsSupported.clear();
+
     // use a random server?
     if (useRandomServer()) {
         _lastUsedServerIndex = qrand() % serverList().size();
@@ -457,16 +462,24 @@ void CoreNetwork::socketInitialized()
         return;
     }
 
-    emit socketOpen(identity, localAddress(), localPort(), peerAddress(), peerPort());
-
     Server server = usedServer();
+
 #ifdef HAVE_SSL
-    if (server.useSsl && !socket.isEncrypted())
+    // 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());
+    }
+
+    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());
 #endif
-    socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
 
-    emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
+    socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
 
     // TokenBucket to avoid sending too much at once
     _messageDelay = 2200;  // this seems to be a safe value (2.2 seconds delay)
@@ -474,9 +487,11 @@ void CoreNetwork::socketInitialized()
     _tokenBucket = _burstSize; // init with a full bucket
     _tokenBucketTimer.start(_messageDelay);
 
-    if (networkInfo().useSasl) {
-        putRawLine(serverEncode(QString("CAP REQ :sasl")));
-    }
+    // 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)));
     }
@@ -488,7 +503,7 @@ void CoreNetwork::socketInitialized()
     else {
         nick = identity->nicks()[0];
     }
-    putRawLine(serverEncode(QString("NICK :%1").arg(nick)));
+    putRawLine(serverEncode(QString("NICK %1").arg(nick)));
     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
 }
 
@@ -850,6 +865,73 @@ void CoreNetwork::setPingInterval(int interval)
     _pingTimer.setInterval(interval * 1000);
 }
 
+/******** IRCv3 Capability Negotiation ********/
+
+void CoreNetwork::addCap(const QString &capability, const QString &value)
+{
+    // Clear from pending list, add to supported list
+    if (!_capsSupported.contains(capability)) {
+        if (value != "") {
+            // Value defined, just use it
+            _capsSupported[capability] = value;
+        } else if (_capsPending.contains(capability)) {
+            // Value not defined, but a pending capability had a value.
+            // E.g. CAP * LS :sasl=PLAIN multi-prefix
+            // Preserve the capability value for later use.
+            _capsSupported[capability] = _capsPending[capability];
+        } else {
+            // No value ever given, assign to blank
+            _capsSupported[capability] = QString();
+        }
+    }
+    if (_capsPending.contains(capability))
+        _capsPending.remove(capability);
+
+    // Handle special cases here
+    // TODO Use events if it makes sense
+}
+
+void CoreNetwork::removeCap(const QString &capability)
+{
+    // Clear from pending list, remove from supported list
+    if (_capsPending.contains(capability))
+        _capsPending.remove(capability);
+    if (_capsSupported.contains(capability))
+        _capsSupported.remove(capability);
+
+    // Handle special cases here
+    // TODO Use events if it makes sense
+}
+
+QString CoreNetwork::capValue(const QString &capability) const
+{
+    // If a supported capability exists, good; if not, return pending value.
+    // If capability isn't supported after all, the pending entry will be removed.
+    if (_capsSupported.contains(capability))
+        return _capsSupported[capability];
+    else if (_capsPending.contains(capability))
+        return _capsPending[capability];
+    else
+        return QString();
+}
+
+void CoreNetwork::queuePendingCap(const QString &capability, const QString &value)
+{
+    if (!_capsQueued.contains(capability)) {
+        _capsQueued.append(capability);
+        // Some capabilities may have values attached, preserve them as pending
+        _capsPending[capability] = value;
+    }
+}
+
+QString CoreNetwork::takeQueuedCap()
+{
+    if (!_capsQueued.empty()) {
+        return _capsQueued.takeFirst();
+    } else {
+        return QString();
+    }
+}
 
 /******** AutoWHO ********/