From 33a1a6f1bf47db069acae3eaa88a1aa156342002 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Sun, 19 Jul 2020 16:28:21 -0400 Subject: [PATCH] common: Allow skipping negotiation of IRCv3 caps Add "skipCaps" to Network, storing a list of IRCv3 capabilities to skip during capability negotiation. This allows selectively disabling capabilities on specific IRC networks. Some networks might not behave as expected, and some IRC features (e.g. "echo-message" support) are enough of a departure from traditional IRC behavior that users may want to turn it off. Add the necessary migrations and setup to SQLite and PostgreSQL. Add the "SkipIrcCaps" feature flag to signal protocol support. Fix up a comment in CoreNetwork referring to an old variable name. Update the SQL README to mention the Serializers protocol checks. Missing this caused me a few hours of confusion over why the protocol change wasn't being received :) --- src/common/network.cpp | 52 ++++++++++++++++++- src/common/network.h | 34 +++++++++++- src/common/quassel.h | 1 + src/common/serializers/serializers.cpp | 1 + src/core/SQL/PostgreSQL/insert_network.sql | 4 +- .../SQL/PostgreSQL/migrate_write_network.sql | 4 +- .../PostgreSQL/select_networks_for_user.sql | 2 +- src/core/SQL/PostgreSQL/setup_040_network.sql | 1 + src/core/SQL/PostgreSQL/update_network.sql | 3 +- ...upgrade_000_alter_network_add_skipcaps.sql | 1 + src/core/SQL/README.md | 4 ++ src/core/SQL/SQLite/insert_network.sql | 4 +- src/core/SQL/SQLite/migrate_read_network.sql | 2 +- .../SQL/SQLite/select_networks_for_user.sql | 2 +- src/core/SQL/SQLite/setup_020_network.sql | 1 + src/core/SQL/SQLite/update_network.sql | 3 +- ...upgrade_000_alter_network_add_skipcaps.sql | 1 + src/core/abstractsqlstorage.h | 1 + src/core/corenetwork.cpp | 49 ++++++++++++++++- src/core/postgresqlstorage.cpp | 5 ++ src/core/sqlitestorage.cpp | 4 ++ 21 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql create mode 100644 src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql diff --git a/src/common/network.cpp b/src/common/network.cpp index 4117bf0f..d6ee3c61 100644 --- a/src/common/network.cpp +++ b/src/common/network.cpp @@ -105,6 +105,7 @@ NetworkInfo Network::networkInfo() const info.serverList = serverList(); info.useRandomServer = useRandomServer(); info.perform = perform(); + info.skipCaps = skipCaps(); info.useAutoIdentify = useAutoIdentify(); info.autoIdentifyService = autoIdentifyService(); info.autoIdentifyPassword = autoIdentifyPassword(); @@ -142,6 +143,8 @@ void Network::setNetworkInfo(const NetworkInfo& info) setUseRandomServer(info.useRandomServer); if (info.perform != perform()) setPerform(info.perform); + if (info.skipCaps != skipCaps()) + setSkipCaps(info.skipCaps); if (info.useAutoIdentify != useAutoIdentify()) setUseAutoIdentify(info.useAutoIdentify); if (info.autoIdentifyService != autoIdentifyService()) @@ -638,6 +641,19 @@ void Network::setPerform(const QStringList& perform) emit configChanged(); } +void Network::setSkipCaps(const QStringList& skipCaps) +{ + _skipCaps = skipCaps; + // Ensure the list of skipped capabilities remains sorted + // + // This becomes important in CoreNetwork::beginCapNegotiation() when finding the intersection of + // available capabilities and skipped capabilities. It's a bit more efficient to sort on first + // initialization and changes afterwards instead of on every (re)connection to the IRC network. + _skipCaps.sort(); + SYNC(ARG(skipCaps)) + emit configChanged(); +} + void Network::setUseAutoIdentify(bool use) { _useAutoIdentify = use; @@ -1109,11 +1125,42 @@ void Network::determinePrefixes() const * NetworkInfo ************************************************************************/ +QString NetworkInfo::skipCapsToString() const { + // Sort the list of capabilities when rendering to a string. This isn't required as + // Network::setSkipCaps() will sort as well, but this looks nicer when displayed to the user. + // This also results in the list being sorted before storing in the database, too. + auto sortedSkipCaps = skipCaps; + sortedSkipCaps.sort(); + + // IRCv3 capabilities are transmitted space-separated, so it should be safe to assume spaces + // won't ever be inside them + // + // See https://ircv3.net/specs/core/capability-negotiation + return sortedSkipCaps.join(" "); +} + +void NetworkInfo::skipCapsFromString(const QString& flattenedSkipCaps) { + // IRCv3 capabilities should all use lowercase capability names, though it's not strictly + // required by the specification. Quassel currently converts all caps to lowercase before doing + // any comparisons. + // + // This would only become an issue if two capabilities have the same name and only differ by + // case, or if an IRC server transmits an uppercase capability and compares case-sensitively. + // + // (QString::toLower() is always done in the C locale, so locale-dependent case-sensitivity + // won't ever be an issue, thankfully.) + // + // See Network::addCap(), Network::acknowledgeCap(), and friends + // And https://ircv3.net/specs/core/capability-negotiation + skipCaps = flattenedSkipCaps.toLower().split(" ", QString::SplitBehavior::SkipEmptyParts); +} + bool NetworkInfo::operator==(const NetworkInfo& other) const { return networkName == other.networkName && serverList == other.serverList && perform == other.perform + && skipCaps == other.skipCaps && autoIdentifyService == other.autoIdentifyService && autoIdentifyPassword == other.autoIdentifyPassword && saslAccount == other.saslAccount @@ -1149,6 +1196,7 @@ QDataStream& operator<<(QDataStream& out, const NetworkInfo& info) i["NetworkName"] = info.networkName; i["ServerList"] = toVariantList(info.serverList); i["Perform"] = info.perform; + i["SkipCaps"] = info.skipCaps; i["AutoIdentifyService"] = info.autoIdentifyService; i["AutoIdentifyPassword"] = info.autoIdentifyPassword; i["SaslAccount"] = info.saslAccount; @@ -1181,6 +1229,7 @@ QDataStream& operator>>(QDataStream& in, NetworkInfo& info) info.networkName = i["NetworkName"].toString(); info.serverList = fromVariantList(i["ServerList"].toList()); info.perform = i["Perform"].toStringList(); + info.skipCaps = i["SkipCaps"].toStringList(); info.autoIdentifyService = i["AutoIdentifyService"].toString(); info.autoIdentifyPassword = i["AutoIdentifyPassword"].toString(); info.saslAccount = i["SaslAccount"].toString(); @@ -1210,7 +1259,8 @@ QDebug operator<<(QDebug dbg, const NetworkInfo& i) dbg.nospace() << "(id = " << i.networkId << " name = " << i.networkName << " identity = " << i.identity << " codecForServer = " << i.codecForServer << " codecForEncoding = " << i.codecForEncoding << " codecForDecoding = " << i.codecForDecoding << " serverList = " << i.serverList - << " useRandomServer = " << i.useRandomServer << " perform = " << i.perform << " useAutoIdentify = " << i.useAutoIdentify + << " useRandomServer = " << i.useRandomServer << " perform = " << i.perform + << " skipCaps = " << i.skipCaps << " useAutoIdentify = " << i.useAutoIdentify << " autoIdentifyService = " << i.autoIdentifyService << " autoIdentifyPassword = " << i.autoIdentifyPassword << " useSasl = " << i.useSasl << " saslAccount = " << i.saslAccount << " saslPassword = " << i.saslPassword << " useAutoReconnect = " << i.useAutoReconnect << " autoReconnectInterval = " << i.autoReconnectInterval diff --git a/src/common/network.h b/src/common/network.h index ac01cc43..22e38847 100644 --- a/src/common/network.h +++ b/src/common/network.h @@ -69,6 +69,7 @@ class COMMON_EXPORT Network : public SyncableObject Q_PROPERTY(int connectionState READ connectionState WRITE setConnectionState) Q_PROPERTY(bool useRandomServer READ useRandomServer WRITE setUseRandomServer) Q_PROPERTY(QStringList perform READ perform WRITE setPerform) + Q_PROPERTY(QStringList skipCaps READ skipCaps WRITE setSkipCaps) Q_PROPERTY(bool useAutoIdentify READ useAutoIdentify WRITE setUseAutoIdentify) Q_PROPERTY(QString autoIdentifyService READ autoIdentifyService WRITE setAutoIdentifyService) Q_PROPERTY(QString autoIdentifyPassword READ autoIdentifyPassword WRITE setAutoIdentifyPassword) @@ -281,6 +282,12 @@ public: inline const ServerList& serverList() const { return _serverList; } inline bool useRandomServer() const { return _useRandomServer; } inline const QStringList& perform() const { return _perform; } + /** + * Gets the list of skipped (not auto-negotiated) capabilities. + * + * @returns QStringList of skippped capabilities + */ + inline const QStringList skipCaps() const { return _skipCaps; } inline bool useAutoIdentify() const { return _useAutoIdentify; } inline const QString& autoIdentifyService() const { return _autoIdentifyService; } inline const QString& autoIdentifyPassword() const { return _autoIdentifyPassword; } @@ -433,6 +440,12 @@ public slots: void setServerList(const QVariantList& serverList); void setUseRandomServer(bool); void setPerform(const QStringList&); + /** + * Sets the list of skipped (not auto-negotiated) capabilities + * + * @param skipCaps QStringList of skipped (not auto-negotiated) capabilities + */ + void setSkipCaps(const QStringList& skipCaps); void setUseAutoIdentify(bool); void setAutoIdentifyService(const QString&); void setAutoIdentifyPassword(const QString&); @@ -531,7 +544,7 @@ public slots: /** * Clears all capabilities from the list of available capabilities. * - * This also removes the capability from the list of acknowledged capabilities. + * This also removes all capabilities from the list of acknowledged capabilities. */ void clearCaps(); @@ -566,7 +579,7 @@ public slots: /** * Initialize the list of enabled (acknowledged) capabilities. * - * @param[in] caps QVariantList of QString indicating enabled (acknowledged) capabilities and values + * @param[in] capsEnabled QVariantList of QString indicating enabled (acknowledged) capabilities */ inline void initSetCapsEnabled(const QVariantList& capsEnabled) { _capsEnabled = fromVariantList(capsEnabled); } inline void initSetServerList(const QVariantList& serverList) { _serverList = fromVariantList(serverList); } @@ -734,9 +747,11 @@ private: QStringList _capsEnabled; /// Enabled capabilities that received 'CAP ACK' // _capsEnabled uses the same values from the = pairs stored in _caps + ServerList _serverList; bool _useRandomServer; QStringList _perform; + QStringList _skipCaps; ///< Capabilities to skip during negotiation (keep list sorted!) bool _useAutoIdentify; QString _autoIdentifyService; @@ -779,6 +794,7 @@ struct COMMON_EXPORT NetworkInfo Network::ServerList serverList; QStringList perform; + QStringList skipCaps; ///< Capabilities to skip during negotiation QString autoIdentifyService{"NickServ"}; QString autoIdentifyPassword; @@ -811,6 +827,20 @@ struct COMMON_EXPORT NetworkInfo public: bool operator==(const NetworkInfo& other) const; bool operator!=(const NetworkInfo& other) const; + + /** + * Gets the list of skipped capabilities in a space-separated string + * + * @see skipCaps + * @return QString representing skipCaps with each cap separated by a space + */ + QString skipCapsToString() const; + /** + * Sets the list of skipped capabilities from a space-separated string + * + * @param flattenedSkipCaps QString representing skipCaps with each cap separated by a space + */ + void skipCapsFromString(const QString& flattenedSkipCaps); }; COMMON_EXPORT QDataStream& operator<<(QDataStream& out, const NetworkInfo& info); diff --git a/src/common/quassel.h b/src/common/quassel.h index bbacce79..7d082ec9 100644 --- a/src/common/quassel.h +++ b/src/common/quassel.h @@ -143,6 +143,7 @@ public: LongMessageId, ///< 64-bit IDs for messages SyncedCoreInfo, ///< CoreInfo dynamically updated using signals LoadBacklogForwards, ///< Allow loading backlog in ascending order, old to new + SkipIrcCaps, ///< Control what IRCv3 capabilities are skipped during negotiation }; Q_ENUMS(Feature) diff --git a/src/common/serializers/serializers.cpp b/src/common/serializers/serializers.cpp index 02bd5149..60759f4c 100644 --- a/src/common/serializers/serializers.cpp +++ b/src/common/serializers/serializers.cpp @@ -255,6 +255,7 @@ bool Serializers::deserialize(QDataStream& stream, const Quassel::Features& feat info.serverList = fromVariantList(i["ServerList"].toList()); info.useRandomServer = i["UseRandomServer"].toBool(); info.perform = i["Perform"].toStringList(); + info.skipCaps = i["SkipCaps"].toStringList(); info.useAutoIdentify = i["UseAutoIdentify"].toBool(); info.autoIdentifyService = i["AutoIdentifyService"].toString(); info.autoIdentifyPassword = i["AutoIdentifyPassword"].toString(); diff --git a/src/core/SQL/PostgreSQL/insert_network.sql b/src/core/SQL/PostgreSQL/insert_network.sql index d91f23c5..9e1d8b7d 100644 --- a/src/core/SQL/PostgreSQL/insert_network.sql +++ b/src/core/SQL/PostgreSQL/insert_network.sql @@ -3,10 +3,10 @@ INSERT INTO network (userid, networkname, identityid, servercodec, encodingcodec autoidentifypassword, useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate, messagerateburstsize, - messageratedelay, unlimitedmessagerate) + messageratedelay, unlimitedmessagerate, skipcaps) VALUES (:userid, :networkname, :identityid, :servercodec, :encodingcodec, :decodingcodec, :userandomserver, :perform, :useautoidentify, :autoidentifyservice, :autoidentifypassword, :useautoreconnect, :autoreconnectinterval, :autoreconnectretries, :unlimitedconnectretries, :rejoinchannels, :usesasl, :saslaccount, :saslpassword, :usecustomessagerate, - :messagerateburstsize, :messageratedelay, :unlimitedmessagerate) + :messagerateburstsize, :messageratedelay, :unlimitedmessagerate, :skipcaps) RETURNING networkid diff --git a/src/core/SQL/PostgreSQL/migrate_write_network.sql b/src/core/SQL/PostgreSQL/migrate_write_network.sql index 979370f0..fd1e5a22 100644 --- a/src/core/SQL/PostgreSQL/migrate_write_network.sql +++ b/src/core/SQL/PostgreSQL/migrate_write_network.sql @@ -4,5 +4,5 @@ INSERT INTO network (networkid, userid, networkname, identityid, encodingcodec, autoreconnectretries, unlimitedconnectretries, rejoinchannels, connected, usermode, awaymessage, attachperform, detachperform, usesasl, saslaccount, saslpassword, usecustomessagerate, messagerateburstsize, messageratedelay, - unlimitedmessagerate) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + unlimitedmessagerate, skipcaps) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) diff --git a/src/core/SQL/PostgreSQL/select_networks_for_user.sql b/src/core/SQL/PostgreSQL/select_networks_for_user.sql index a5395dc7..adf490b5 100644 --- a/src/core/SQL/PostgreSQL/select_networks_for_user.sql +++ b/src/core/SQL/PostgreSQL/select_networks_for_user.sql @@ -2,6 +2,6 @@ SELECT networkid, networkname, identityid, servercodec, encodingcodec, decodingc userandomserver, perform, useautoidentify, autoidentifyservice, autoidentifypassword, useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate, - messagerateburstsize, messageratedelay, unlimitedmessagerate + messagerateburstsize, messageratedelay, unlimitedmessagerate, skipcaps FROM network WHERE userid = :userid diff --git a/src/core/SQL/PostgreSQL/setup_040_network.sql b/src/core/SQL/PostgreSQL/setup_040_network.sql index f1f3dbf7..9e39f071 100644 --- a/src/core/SQL/PostgreSQL/setup_040_network.sql +++ b/src/core/SQL/PostgreSQL/setup_040_network.sql @@ -28,5 +28,6 @@ CREATE TABLE network ( messagerateburstsize INTEGER NOT NULL DEFAULT 5, -- Maximum messages at once messageratedelay INTEGER NOT NULL DEFAULT 2200, -- Delay between future messages (milliseconds) unlimitedmessagerate boolean NOT NULL DEFAULT FALSE, -- Disable rate limits + skipcaps TEXT, -- Space-separated IRCv3 caps to not auto-negotiate UNIQUE (userid, networkname) ) diff --git a/src/core/SQL/PostgreSQL/update_network.sql b/src/core/SQL/PostgreSQL/update_network.sql index e087e557..83dd20db 100644 --- a/src/core/SQL/PostgreSQL/update_network.sql +++ b/src/core/SQL/PostgreSQL/update_network.sql @@ -20,6 +20,7 @@ unlimitedmessagerate = :unlimitedmessagerate, rejoinchannels = :rejoinchannels, usesasl = :usesasl, saslaccount = :saslaccount, -saslpassword = :saslpassword +saslpassword = :saslpassword, +skipcaps = :skipcaps WHERE userid = :userid AND networkid = :networkid diff --git a/src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql b/src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql new file mode 100644 index 00000000..b67cc0df --- /dev/null +++ b/src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql @@ -0,0 +1 @@ +ALTER TABLE network ADD COLUMN skipcaps TEXT diff --git a/src/core/SQL/README.md b/src/core/SQL/README.md index 31e43f85..f7151d7b 100644 --- a/src/core/SQL/README.md +++ b/src/core/SQL/README.md @@ -168,6 +168,9 @@ In client-side code, test for a feature with... if (Client::isCoreFeatureEnabled(Quassel::Feature::FeatureName)) { ... } ``` +Depending on the type of protocol change, you might also need to update +[`serializers.cpp`][file-cpp-serializers] and related files. + 8. **Test everything! Upgrade, migrate, new setups, new client/old core, old client/new core, etc.** @@ -245,3 +248,4 @@ Thank you for reading this guide and good luck with your changes! [file-cpp-sqlite]: ../sqlitestorage.cpp [file-sh-upgradeschema]: upgradeSchema.sh [file-h-quassel]: ../../common/quassel.h +[file-cpp-serializers]: ../../common/serializers/serializers.cpp diff --git a/src/core/SQL/SQLite/insert_network.sql b/src/core/SQL/SQLite/insert_network.sql index ec5a212c..1db37741 100644 --- a/src/core/SQL/SQLite/insert_network.sql +++ b/src/core/SQL/SQLite/insert_network.sql @@ -3,9 +3,9 @@ INSERT INTO network (userid, networkname, identityid, servercodec, encodingcodec autoidentifypassword, useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate, messagerateburstsize, - messageratedelay, unlimitedmessagerate) + messageratedelay, unlimitedmessagerate, skipcaps) VALUES (:userid, :networkname, :identityid, :servercodec, :encodingcodec, :decodingcodec, :userandomserver, :perform, :useautoidentify, :autoidentifyservice, :autoidentifypassword, :useautoreconnect, :autoreconnectinterval, :autoreconnectretries, :unlimitedconnectretries, :rejoinchannels, :usesasl, :saslaccount, :saslpassword, :usecustomessagerate, - :messagerateburstsize, :messageratedelay, :unlimitedmessagerate) + :messagerateburstsize, :messageratedelay, :unlimitedmessagerate, :skipcaps) diff --git a/src/core/SQL/SQLite/migrate_read_network.sql b/src/core/SQL/SQLite/migrate_read_network.sql index cccaed9c..8bbcb03f 100644 --- a/src/core/SQL/SQLite/migrate_read_network.sql +++ b/src/core/SQL/SQLite/migrate_read_network.sql @@ -3,5 +3,5 @@ SELECT networkid, userid, networkname, identityid, encodingcodec, decodingcodec, useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries, rejoinchannels, connected, usermode, awaymessage, attachperform, detachperform, usesasl, saslaccount, saslpassword, usecustomessagerate, messagerateburstsize, - messageratedelay, unlimitedmessagerate + messageratedelay, unlimitedmessagerate, skipcaps FROM network diff --git a/src/core/SQL/SQLite/select_networks_for_user.sql b/src/core/SQL/SQLite/select_networks_for_user.sql index a5395dc7..adf490b5 100644 --- a/src/core/SQL/SQLite/select_networks_for_user.sql +++ b/src/core/SQL/SQLite/select_networks_for_user.sql @@ -2,6 +2,6 @@ SELECT networkid, networkname, identityid, servercodec, encodingcodec, decodingc userandomserver, perform, useautoidentify, autoidentifyservice, autoidentifypassword, useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate, - messagerateburstsize, messageratedelay, unlimitedmessagerate + messagerateburstsize, messageratedelay, unlimitedmessagerate, skipcaps FROM network WHERE userid = :userid diff --git a/src/core/SQL/SQLite/setup_020_network.sql b/src/core/SQL/SQLite/setup_020_network.sql index 60417a73..239f22bd 100644 --- a/src/core/SQL/SQLite/setup_020_network.sql +++ b/src/core/SQL/SQLite/setup_020_network.sql @@ -28,5 +28,6 @@ CREATE TABLE network ( messagerateburstsize INTEGER NOT NULL DEFAULT 5, -- Maximum messages at once messageratedelay INTEGER NOT NULL DEFAULT 2200, -- Delay between future messages (milliseconds) unlimitedmessagerate INTEGER NOT NULL DEFAULT 0, -- BOOL - Disable rate limits + skipcaps TEXT, -- Space-separated IRCv3 caps to not auto-negotiate UNIQUE (userid, networkname) ) diff --git a/src/core/SQL/SQLite/update_network.sql b/src/core/SQL/SQLite/update_network.sql index 00e9f83e..d5110e24 100644 --- a/src/core/SQL/SQLite/update_network.sql +++ b/src/core/SQL/SQLite/update_network.sql @@ -20,5 +20,6 @@ messageratedelay = :messageratedelay, unlimitedmessagerate = :unlimitedmessagerate, usesasl = :usesasl, saslaccount = :saslaccount, -saslpassword = :saslpassword +saslpassword = :saslpassword, +skipcaps = :skipcaps WHERE networkid = :networkid AND userid = :userid diff --git a/src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql b/src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql new file mode 100644 index 00000000..b67cc0df --- /dev/null +++ b/src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql @@ -0,0 +1 @@ +ALTER TABLE network ADD COLUMN skipcaps TEXT diff --git a/src/core/abstractsqlstorage.h b/src/core/abstractsqlstorage.h index c2521f55..e422795b 100644 --- a/src/core/abstractsqlstorage.h +++ b/src/core/abstractsqlstorage.h @@ -273,6 +273,7 @@ public: QString awaymessage; QString attachperform; QString detachperform; + QString skipcaps; NetworkId networkid; IdentityId identityid; int messagerateburstsize; diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index 9a8c6361..438a42a7 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -20,6 +20,8 @@ #include "corenetwork.h" +#include + #include #include #include @@ -1075,6 +1077,11 @@ void CoreNetwork::resetTokenBucket() void CoreNetwork::serverCapAdded(const QString& capability) { + // Exclude skipped capabilities + if (skipCaps().contains(capability)) { + return; + } + // Check if it's a known capability; if so, add it to the list // Handle special cases first if (capability == IrcCap::SASL) { @@ -1099,7 +1106,7 @@ void CoreNetwork::serverCapAcknowledged(const QString& capability) } // Handle capabilities that require further messages sent to the IRC server - // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages + // If you change this list, ALSO change the list in CoreNetwork::capsRequiringConfiguration if (capability == IrcCap::SASL) { // If SASL mechanisms specified, limit to what's accepted for authentication // if the current identity has a cert set, use SASL EXTERNAL @@ -1253,6 +1260,26 @@ void CoreNetwork::retryCapsIndividually() void CoreNetwork::beginCapNegotiation() { + // Check if any available capabilities have been disabled + QStringList capsSkipped; + if (!skipCaps().isEmpty() && !caps().isEmpty()) { + // Find the entries that are common to skipCaps() and caps(). This represents any + // capabilities supported by the server that were skipped. + + // Both skipCaps() and caps() are already lowercase + // std::set_intersection requires sorted lists, and we can't modify the original lists. + // + // skipCaps() should already be sorted. caps() is intentionally not sorted elsewhere so + // Quassel can show the capabilities in the order transmitted by the network. + auto sortedCaps = caps(); + sortedCaps.sort(); + + // Find the intersection between skipped caps and server-supplied caps + std::set_intersection(skipCaps().cbegin(), skipCaps().cend(), + sortedCaps.cbegin(), sortedCaps.cend(), + std::back_inserter(capsSkipped)); + } + if (!capsPendingNegotiation()) { // No capabilities are queued for request, determine the reason why QString capStatusMsg; @@ -1283,6 +1310,16 @@ void CoreNetwork::beginCapNegotiation() capStatusMsg )); + if (!capsSkipped.isEmpty()) { + // Mention that some capabilities are skipped + showMessage(NetworkInternalMessage( + Message::Server, + BufferInfo::StatusBuffer, + "", + tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", ")) + )); + } + // End any ongoing capability negotiation, allowing connection to continue endCapNegotiation(); return; @@ -1296,6 +1333,16 @@ void CoreNetwork::beginCapNegotiation() tr("Ready to negotiate (found: %1)").arg(caps().join(", ")) )); + if (!capsSkipped.isEmpty()) { + // Mention that some capabilities are skipped + showMessage(NetworkInternalMessage( + Message::Server, + BufferInfo::StatusBuffer, + "", + tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", ")) + )); + } + // Build a list of queued capabilities, starting with individual, then bundled, only adding the // comma separator between the two if needed (both individual and bundled caps exist). QString queuedCapsDisplay = _capsQueuedIndividual.join(", ") diff --git a/src/core/postgresqlstorage.cpp b/src/core/postgresqlstorage.cpp index 36301c6b..2c238ba1 100644 --- a/src/core/postgresqlstorage.cpp +++ b/src/core/postgresqlstorage.cpp @@ -800,6 +800,8 @@ void PostgreSqlStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& inf query.bindValue(":messagerateburstsize", info.messageRateBurstSize); query.bindValue(":messageratedelay", info.messageRateDelay); query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate); + query.bindValue(":skipcaps", info.skipCapsToString()); + if (info.networkId.isValid()) query.bindValue(":networkid", info.networkId.toInt()); } @@ -947,6 +949,7 @@ std::vector PostgreSqlStorage::networks(UserId user) net.messageRateBurstSize = networksQuery.value(20).toUInt(); net.messageRateDelay = networksQuery.value(21).toUInt(); net.unlimitedMessageRate = networksQuery.value(22).toBool(); + net.skipCapsFromString(networksQuery.value(23).toString()); serversQuery.bindValue(":networkid", net.networkId.toInt()); safeExec(serversQuery); @@ -2424,6 +2427,8 @@ bool PostgreSqlMigrationWriter::writeMo(const NetworkMO& network) bindValue(26, network.messagerateburstsize); bindValue(27, network.messageratedelay); bindValue(28, network.unlimitedmessagerate); + // Skipped IRCv3 caps + bindValue(29, network.skipcaps); return exec(); } diff --git a/src/core/sqlitestorage.cpp b/src/core/sqlitestorage.cpp index df5033a7..8ee8f880 100644 --- a/src/core/sqlitestorage.cpp +++ b/src/core/sqlitestorage.cpp @@ -773,6 +773,7 @@ void SqliteStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& info) query.bindValue(":messagerateburstsize", info.messageRateBurstSize); query.bindValue(":messageratedelay", info.messageRateDelay); query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate ? 1 : 0); + query.bindValue(":skipcaps", info.skipCapsToString()); if (info.networkId.isValid()) query.bindValue(":networkid", info.networkId.toInt()); } @@ -970,6 +971,7 @@ std::vector SqliteStorage::networks(UserId user) net.messageRateBurstSize = networksQuery.value(20).toUInt(); net.messageRateDelay = networksQuery.value(21).toUInt(); net.unlimitedMessageRate = networksQuery.value(22).toInt() == 1 ? true : false; + net.skipCapsFromString(networksQuery.value(23).toString()); serversQuery.bindValue(":networkid", net.networkId.toInt()); safeExec(serversQuery); @@ -2494,6 +2496,8 @@ bool SqliteMigrationReader::readMo(NetworkMO& network) network.messagerateburstsize = value(26).toInt(); network.messageratedelay = value(27).toUInt(); network.unlimitedmessagerate = value(28).toInt() == 1 ? true : false; + // Skipped IRCv3 caps + network.skipcaps = value(29).toString(); return true; } -- 2.20.1