- // 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
+ // IRCv3 specs all use lowercase capability names
+ QString _capLowercase = capability.toLower();
+
+ if(capsRequiringConfiguration.contains(_capLowercase)) {
+ // The capability requires additional configuration before being acknowledged (e.g. SASL),
+ // so we should negotiate it separately from all other capabilities. Otherwise new
+ // capabilities will be requested while still configuring the previous one.
+ if (!_capsQueuedIndividual.contains(_capLowercase)) {
+ _capsQueuedIndividual.append(_capLowercase);
+ }
+ } else {
+ // The capability doesn't need any special configuration, so it should be safe to try
+ // bundling together with others. "Should" being the imperative word, as IRC servers can do
+ // anything.
+ if (!_capsQueuedBundled.contains(_capLowercase)) {
+ _capsQueuedBundled.append(_capLowercase);
+ }
+ }
+}
+
+QString CoreNetwork::takeQueuedCaps()
+{
+ // Clear the record of the most recently negotiated capability bundle. Does nothing if the list
+ // is empty.
+ _capsQueuedLastBundle.clear();
+
+ // First, negotiate all the standalone capabilities that require additional configuration.
+ if (!_capsQueuedIndividual.empty()) {
+ // We have an individual capability available. Take the first and pass it back.
+ return _capsQueuedIndividual.takeFirst();
+ } else if (!_capsQueuedBundled.empty()) {
+ // We have capabilities available that can be grouped. Try to fit in as many as within the
+ // maximum length.
+ // See CoreNetwork::maxCapRequestLength
+
+ // Response must have at least one capability regardless of max length for anything to
+ // happen.
+ QString capBundle = _capsQueuedBundled.takeFirst();
+ QString nextCap("");
+ while (!_capsQueuedBundled.empty()) {
+ // As long as capabilities remain, get the next...
+ nextCap = _capsQueuedBundled.first();
+ if ((capBundle.length() + 1 + nextCap.length()) <= maxCapRequestLength) {
+ // [capability + 1 for a space + this new capability] fit within length limits
+ // Add it to formatted list
+ capBundle.append(" " + nextCap);
+ // Add it to most recent bundle of requested capabilities (simplifies retry logic)
+ _capsQueuedLastBundle.append(nextCap);
+ // Then remove it from the queue
+ _capsQueuedBundled.removeFirst();
+ } else {
+ // We've reached the length limit for a single capability request, stop adding more
+ break;
+ }
+ }
+ // Return this space-separated set of capabilities, removing any extra spaces
+ return capBundle.trimmed();
+ } else {
+ // No capabilities left to negotiate, return an empty string.