File indexing completed on 2024-04-21 04:04:41
0001 /* 0002 jabbercapabilitiesmanager.cpp - Manage entity capabilities(JEP-0115). 0003 0004 Copyright (c) 2006 by Michaƫl Larouche <larouche@kde.org> 0005 Copyright 2006 by Tommi Rantala <tommi.rantala@cs.helsinki.fi> 0006 0007 Kopete (c) 2001-2006 by the Kopete developers <kopete-devel@kde.org> 0008 0009 Imported from caps.cpp from Psi: 0010 Copyright (C) 2005 Remko Troncon 0011 0012 ************************************************************************* 0013 * * 0014 * This program is free software; you can redistribute it and/or modify * 0015 * it under the terms of the GNU General Public License as published by * 0016 * the Free Software Foundation; either either version 2 0017 of the License, or (at your option) any later version.of the License, or * 0018 * (at your option) any later version. * 0019 * * 0020 ************************************************************************* 0021 */ 0022 #include "jabbercapabilitiesmanager.h" 0023 #include "jabber_protocol_debug.h" 0024 0025 #include <QString> 0026 #include <QTimer> 0027 #include <qdom.h> 0028 #include <QTextStream> 0029 0030 #include <KStandardDirs> 0031 0032 #include <xmpp_tasks.h> 0033 0034 #include "jabberaccount.h" 0035 #include "jabberprotocol.h" 0036 0037 using namespace XMPP; 0038 0039 //BEGIN Capabilities 0040 JabberCapabilitiesManager::Capabilities::Capabilities() 0041 {} 0042 0043 JabberCapabilitiesManager::Capabilities::Capabilities(const QString& node, const QString& version, const QString& extensions) 0044 : m_node(node), m_version(version), m_extensions(extensions) 0045 {} 0046 0047 const QString& JabberCapabilitiesManager::Capabilities::node() const 0048 { 0049 return m_node; 0050 } 0051 0052 const QString& JabberCapabilitiesManager::Capabilities::version() const 0053 { 0054 return m_version; 0055 } 0056 0057 const QString& JabberCapabilitiesManager::Capabilities::extensions() const 0058 { 0059 return m_extensions; 0060 } 0061 0062 JabberCapabilitiesManager::CapabilitiesList JabberCapabilitiesManager::Capabilities::flatten() const 0063 { 0064 CapabilitiesList capsList; 0065 capsList.append( Capabilities(node(), version(), version()) ); 0066 0067 QStringList extensionList = extensions().split(' '); 0068 0069 foreach(QStringList::const_reference str, extensionList) 0070 { 0071 capsList.append( Capabilities(node(), version(), str) ); 0072 } 0073 0074 return capsList; 0075 } 0076 0077 bool JabberCapabilitiesManager::Capabilities::operator==(const Capabilities &other) const 0078 { 0079 return (node() == other.node() && version() == other.version() && extensions() == other.extensions()); 0080 } 0081 0082 bool JabberCapabilitiesManager::Capabilities::operator!=(const Capabilities &other) const 0083 { 0084 return !((*this) == other); 0085 } 0086 0087 bool JabberCapabilitiesManager::Capabilities::operator<(const Capabilities &other) const 0088 { 0089 return (node() != other.node() ? node() < other.node() : 0090 (version() != other.version() ? version() < other.version() : 0091 extensions() < other.extensions())); 0092 } 0093 //END Capabilities 0094 0095 //BEGIN CapabilitiesInformation 0096 JabberCapabilitiesManager::CapabilitiesInformation::CapabilitiesInformation() 0097 : m_discovered(false), m_pendingRequests(0) 0098 { 0099 updateLastSeen(); 0100 } 0101 0102 const QStringList& JabberCapabilitiesManager::CapabilitiesInformation::features() const 0103 { 0104 return m_features; 0105 } 0106 0107 const DiscoItem::Identities& JabberCapabilitiesManager::CapabilitiesInformation::identities() const 0108 { 0109 return m_identities; 0110 } 0111 0112 QStringList JabberCapabilitiesManager::CapabilitiesInformation::jids() const 0113 { 0114 QStringList jids; 0115 0116 foreach(JidList::const_reference pair, m_jids) 0117 { 0118 QString jid = pair.first; 0119 0120 if( !jids.contains(jid) ) 0121 jids.push_back(jid); 0122 } 0123 0124 return jids; 0125 } 0126 0127 bool JabberCapabilitiesManager::CapabilitiesInformation::discovered() const 0128 { 0129 return m_discovered; 0130 } 0131 0132 int JabberCapabilitiesManager::CapabilitiesInformation::pendingRequests() const 0133 { 0134 return m_pendingRequests; 0135 } 0136 0137 void JabberCapabilitiesManager::CapabilitiesInformation::reset() 0138 { 0139 m_features.clear(); 0140 m_identities.clear(); 0141 m_discovered = false; 0142 } 0143 0144 void JabberCapabilitiesManager::CapabilitiesInformation::removeAccount(JabberAccount *account) 0145 { 0146 JidList::Iterator it = m_jids.begin(); 0147 while( it != m_jids.end() ) 0148 { 0149 if( (*it).second == account) 0150 { 0151 it = m_jids.erase(it); 0152 } 0153 else 0154 { 0155 it++; 0156 } 0157 } 0158 } 0159 0160 void JabberCapabilitiesManager::CapabilitiesInformation::addJid(const Jid& jid, JabberAccount* account) 0161 { 0162 QPair<QString, JabberAccount*> jidAccountPair(jid.full(),account); 0163 0164 if( !m_jids.contains(jidAccountPair) ) 0165 { 0166 m_jids.push_back(jidAccountPair); 0167 updateLastSeen(); 0168 } 0169 } 0170 0171 void JabberCapabilitiesManager::CapabilitiesInformation::removeJid(const Jid& jid) 0172 { 0173 qCDebug(JABBER_PROTOCOL_LOG) << "Unregistering " << QString(jid.full()).replace('%',"%%"); 0174 0175 JidList::Iterator it = m_jids.begin(); 0176 while( it != m_jids.end() ) 0177 { 0178 if( (*it).first == jid.full() ) 0179 { 0180 it = m_jids.erase(it); 0181 } 0182 else 0183 { 0184 it++; 0185 } 0186 } 0187 } 0188 0189 QPair<Jid,JabberAccount*> JabberCapabilitiesManager::CapabilitiesInformation::nextJid(const Jid& jid, const Task* t) 0190 { 0191 qCDebug(JABBER_PROTOCOL_LOG) << "Looking for next JID"; 0192 0193 JidList::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd(); 0194 for( ; it != itEnd; ++it) 0195 { 0196 if( (*it).first == jid.full() && (*it).second->client()->rootTask() == t) 0197 { 0198 it++; 0199 if (it == itEnd) 0200 { 0201 qCDebug(JABBER_PROTOCOL_LOG) << "No more JIDs"; 0202 0203 return QPair<Jid,JabberAccount*>(Jid(),0L); 0204 } 0205 else if( (*it).second->isConnected() ) 0206 { 0207 //qDebug("caps.cpp: Account isn't active"); 0208 qCDebug(JABBER_PROTOCOL_LOG) << "Account isn't connected."; 0209 0210 return QPair<Jid,JabberAccount*>( (*it).first,(*it).second ); 0211 } 0212 } 0213 } 0214 return QPair<Jid,JabberAccount*>(Jid(),0L); 0215 } 0216 0217 void JabberCapabilitiesManager::CapabilitiesInformation::setDiscovered(bool value) 0218 { 0219 m_discovered = value; 0220 } 0221 0222 void JabberCapabilitiesManager::CapabilitiesInformation::setPendingRequests(int pendingRequests) 0223 { 0224 m_pendingRequests = pendingRequests; 0225 } 0226 0227 void JabberCapabilitiesManager::CapabilitiesInformation::setIdentities(const DiscoItem::Identities& identities) 0228 { 0229 m_identities = identities; 0230 } 0231 0232 void JabberCapabilitiesManager::CapabilitiesInformation::setFeatures(const QStringList& featureList) 0233 { 0234 m_features = featureList; 0235 } 0236 0237 void JabberCapabilitiesManager::CapabilitiesInformation::updateLastSeen() 0238 { 0239 m_lastSeen = QDate::currentDate(); 0240 } 0241 0242 QDomElement JabberCapabilitiesManager::CapabilitiesInformation::toXml(QDomDocument *doc) const 0243 { 0244 QDomElement info = doc->createElement("info"); 0245 //info.setAttribute("last-seen",lastSeen_.toString(Qt::ISODate)); 0246 0247 // Identities 0248 foreach(DiscoItem::Identities::const_reference ident, m_identities) 0249 { 0250 QDomElement identity = doc->createElement("identity"); 0251 identity.setAttribute("category", ident.category); 0252 identity.setAttribute("name", ident.name); 0253 identity.setAttribute("type", ident.type); 0254 info.appendChild(identity); 0255 } 0256 0257 // Features 0258 foreach(QStringList::const_reference feat, m_features) 0259 { 0260 QDomElement feature = doc->createElement("feature"); 0261 feature.setAttribute("node", feat); 0262 info.appendChild(feature); 0263 } 0264 0265 return info; 0266 } 0267 0268 void JabberCapabilitiesManager::CapabilitiesInformation::fromXml(const QDomElement &element) 0269 { 0270 if( element.tagName() != "info") 0271 { 0272 qCDebug(JABBER_PROTOCOL_LOG) << "Invalid info element"; 0273 return; 0274 } 0275 0276 //if (!e.attribute("last-seen").isEmpty()) 0277 // lastSeen_ = QDate::fromString(e.attribute("last-seen"),Qt::ISODate); 0278 0279 for(QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) 0280 { 0281 QDomElement infoElement = node.toElement(); 0282 if( infoElement.isNull() ) 0283 { 0284 qCDebug(JABBER_PROTOCOL_LOG) << "Null element"; 0285 continue; 0286 } 0287 0288 if( infoElement.tagName() == "identity") 0289 { 0290 DiscoItem::Identity id; 0291 id.category = infoElement.attribute("category"); 0292 id.name = infoElement.attribute("name"); 0293 id.type = infoElement.attribute("type"); 0294 m_identities += id; 0295 } 0296 else if( infoElement.tagName() == "feature" ) 0297 { 0298 m_features += infoElement.attribute("node"); 0299 } 0300 else 0301 { 0302 qCDebug(JABBER_PROTOCOL_LOG) << "Unknown element"; 0303 } 0304 0305 m_discovered = true; 0306 } 0307 } 0308 //END CapabilitiesInformation 0309 0310 //BEGIN Private(d-ptr) 0311 class JabberCapabilitiesManager::Private 0312 { 0313 public: 0314 Private() 0315 {} 0316 0317 // Map a full jid to a capabilities 0318 QMap<QString,Capabilities> jidCapabilitiesMap; 0319 // Map a capabilities to its detail information 0320 QMap<Capabilities,CapabilitiesInformation> capabilitiesInformationMap; 0321 }; 0322 //END Private(d-ptr) 0323 0324 JabberCapabilitiesManager::JabberCapabilitiesManager() 0325 : d(new Private) 0326 { 0327 } 0328 0329 JabberCapabilitiesManager::~JabberCapabilitiesManager() 0330 { 0331 saveInformation(); 0332 delete d; 0333 } 0334 0335 void JabberCapabilitiesManager::removeAccount(JabberAccount *account) 0336 { 0337 qCDebug(JABBER_PROTOCOL_LOG) << "Removing account " << account->accountId(); 0338 0339 QList<CapabilitiesInformation> info = d->capabilitiesInformationMap.values(); 0340 0341 foreach(CapabilitiesInformation cap, info) 0342 { 0343 cap.removeAccount(account); 0344 } 0345 } 0346 0347 void JabberCapabilitiesManager::updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status ) 0348 { 0349 if( !account->client() || !account->client()->rootTask() ) 0350 return; 0351 0352 0353 // Do don't anything if the jid correspond to the account's JabberClient jid. 0354 // false means that we don't check for resources. 0355 if( jid.compare(account->client()->jid(), false) ) 0356 return; 0357 0358 QString node = status.capsNode(), version = status.capsVersion(), extensions = status.capsExt(); 0359 Capabilities capabilities( node, version, extensions ); 0360 0361 // Check if the capabilities was really updated(i.e the content is different) 0362 if( d->jidCapabilitiesMap[jid.full()] != capabilities) 0363 { 0364 // Unregister from all old caps nodes 0365 // FIXME: We should only unregister & register from changed nodes 0366 CapabilitiesList oldCaps = d->jidCapabilitiesMap[jid.full()].flatten(); 0367 CapabilitiesList::Iterator oldCapsIt = oldCaps.begin(), oldCapsItEnd = oldCaps.end(); 0368 for( ; oldCapsIt != oldCapsItEnd; ++oldCapsIt) 0369 { 0370 if( (*oldCapsIt) != Capabilities() ) 0371 { 0372 d->capabilitiesInformationMap[*oldCapsIt].removeJid(jid); 0373 } 0374 } 0375 0376 // Check if the jid has caps in his presence message. 0377 if( !status.capsNode().isEmpty() && !status.capsVersion().isEmpty() ) 0378 { 0379 // Register with all new caps nodes 0380 d->jidCapabilitiesMap[jid.full()] = capabilities; 0381 CapabilitiesList caps = capabilities.flatten(); 0382 CapabilitiesList::Iterator newCapsIt = caps.begin(), newCapsItEnd = caps.end(); 0383 for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) 0384 { 0385 d->capabilitiesInformationMap[*newCapsIt].addJid(jid,account); 0386 } 0387 0388 emit capabilitiesChanged(jid); 0389 0390 // Register new caps and check if we need to discover features 0391 newCapsIt = caps.begin(); 0392 for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) 0393 { 0394 if( !d->capabilitiesInformationMap[*newCapsIt].discovered() && d->capabilitiesInformationMap[*newCapsIt].pendingRequests() == 0 ) 0395 { 0396 qCDebug(JABBER_PROTOCOL_LOG) << QString("Sending disco request to %1, node=%2").arg(QString(jid.full()).replace('%',"%%")).arg(node + '#' + (*newCapsIt).extensions()); 0397 0398 d->capabilitiesInformationMap[*newCapsIt].setPendingRequests(1); 0399 requestDiscoInfo(account, jid, node + '#' + (*newCapsIt).extensions()); 0400 } 0401 } 0402 } 0403 else 0404 { 0405 // Remove all caps specifications 0406 qCDebug(JABBER_PROTOCOL_LOG) << QString("Illegal caps info from %1: node=%2, ver=%3").arg(QString(jid.full()).replace('%',"%%")).arg(node).arg(version); 0407 0408 d->jidCapabilitiesMap.remove( jid.full() ); 0409 } 0410 } 0411 else 0412 { 0413 // Add to the list of jids 0414 CapabilitiesList caps = capabilities.flatten(); 0415 CapabilitiesList::Iterator capsIt = caps.begin(), capsItEnd = caps.end(); 0416 for( ; capsIt != capsItEnd; ++capsIt) 0417 { 0418 d->capabilitiesInformationMap[*capsIt].addJid(jid,account); 0419 } 0420 } 0421 } 0422 0423 void JabberCapabilitiesManager::requestDiscoInfo(JabberAccount *account, const Jid& jid, const QString& node) 0424 { 0425 if( !account->client()->rootTask() ) 0426 return; 0427 0428 JT_DiscoInfo *discoInfo = new JT_DiscoInfo(account->client()->rootTask()); 0429 connect(discoInfo, SIGNAL(finished()), SLOT(discoRequestFinished())); 0430 discoInfo->get(jid, node); 0431 //pending_++; 0432 //timer_.start(REQUEST_TIMEOUT,true); 0433 discoInfo->go(true); 0434 } 0435 0436 void JabberCapabilitiesManager::discoRequestFinished() 0437 { 0438 JT_DiscoInfo *discoInfo = (JT_DiscoInfo*)sender(); 0439 if (!discoInfo) 0440 return; 0441 0442 DiscoItem item = discoInfo->item(); 0443 Jid jid = discoInfo->jid(); 0444 qCDebug(JABBER_PROTOCOL_LOG) << QString("Disco response from %1, node=%2, success=%3").arg(QString(jid.full()).replace('%',"%%")).arg(discoInfo->node()).arg(discoInfo->success()); 0445 0446 const QString &tokens = discoInfo->node(); 0447 int idx = tokens.lastIndexOf('#'); 0448 0449 if (idx < 0) 0450 return; 0451 0452 // Update features 0453 QString node = tokens.left(idx); 0454 QString extensions = tokens.mid(idx + 1); 0455 0456 Capabilities jidCapabilities = d->jidCapabilitiesMap[jid.full()]; 0457 if( jidCapabilities.node() == node ) 0458 { 0459 Capabilities capabilities(node, jidCapabilities.version(), extensions); 0460 0461 if( discoInfo->success() ) 0462 { 0463 // Save identities & features 0464 d->capabilitiesInformationMap[capabilities].setIdentities(item.identities()); 0465 d->capabilitiesInformationMap[capabilities].setFeatures(item.features().list()); 0466 d->capabilitiesInformationMap[capabilities].setPendingRequests(0); 0467 d->capabilitiesInformationMap[capabilities].setDiscovered(true); 0468 0469 // Save(Cache) information 0470 saveInformation(); 0471 0472 // Notify affected jids. 0473 QStringList jids = d->capabilitiesInformationMap[capabilities].jids(); 0474 qCDebug(JABBER_PROTOCOL_LOG) << "notify affected jids"; 0475 foreach( QString jid , jids ) 0476 { 0477 emit capabilitiesChanged(jid); 0478 } 0479 } 0480 else 0481 { 0482 QPair<Jid,JabberAccount*> jidAccountPair = d->capabilitiesInformationMap[capabilities].nextJid(jid,discoInfo->parent()); 0483 if( jidAccountPair.second ) 0484 { 0485 qCDebug(JABBER_PROTOCOL_LOG) << QString("Falling back on %1.").arg(QString(jidAccountPair.first.full()).replace('%',"%%")); 0486 requestDiscoInfo( jidAccountPair.second, jidAccountPair.first, discoInfo->node() ); 0487 } 0488 else 0489 { 0490 qCDebug(JABBER_PROTOCOL_LOG) << "No valid disco request avalable."; 0491 d->capabilitiesInformationMap[capabilities].setPendingRequests(0); 0492 } 0493 } 0494 } 0495 else 0496 qCDebug(JABBER_PROTOCOL_LOG) << QString("Current client node '%1' does not match response '%2'").arg(jidCapabilities.node()).arg(node); 0497 0498 //for (unsigned int i = 0; i < item.features().list().count(); i++) 0499 // printf(" Feature: %s\n",item.features().list()[i].toLatin1()); 0500 0501 // Check pending requests 0502 // pending_ = (pending_ > 0 ? pending_-1 : 0); 0503 // if (!pending_) { 0504 // timer_.stop(); 0505 // updatePendingJIDs(); 0506 // } 0507 } 0508 0509 void JabberCapabilitiesManager::loadCachedInformation() 0510 { 0511 QString capsFileName; 0512 capsFileName = KStandardDirs::locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); 0513 0514 // Load settings 0515 QDomDocument doc; 0516 QFile cacheFile(capsFileName); 0517 if( !cacheFile.open(QIODevice::ReadOnly) ) 0518 { 0519 qCDebug(JABBER_PROTOCOL_LOG) << "Could not open the Capabilities cache from disk."; 0520 return; 0521 } 0522 if( !doc.setContent(&cacheFile) ) 0523 { 0524 qCDebug(JABBER_PROTOCOL_LOG) << "Could not set the Capabilities cache from file."; 0525 return; 0526 } 0527 cacheFile.close(); 0528 0529 QDomElement caps = doc.documentElement(); 0530 if( caps.tagName() != "capabilities" ) 0531 { 0532 qCDebug(JABBER_PROTOCOL_LOG) << "Invalid capabilities element."; 0533 return; 0534 } 0535 0536 QDomNode node; 0537 for(node = caps.firstChild(); !node.isNull(); node = node.nextSibling()) 0538 { 0539 QDomElement element = node.toElement(); 0540 if( element.isNull() ) 0541 { 0542 qCDebug(JABBER_PROTOCOL_LOG) << "Found a null element."; 0543 continue; 0544 } 0545 0546 if( element.tagName() == "info" ) 0547 { 0548 CapabilitiesInformation info; 0549 info.fromXml(element); 0550 Capabilities entityCaps( element.attribute("node"),element.attribute("ver"),element.attribute("ext") ); 0551 d->capabilitiesInformationMap[entityCaps] = info; 0552 } 0553 else 0554 { 0555 qCDebug(JABBER_PROTOCOL_LOG) << "Unknow element"; 0556 } 0557 } 0558 } 0559 0560 bool JabberCapabilitiesManager::capabilitiesEnabled(const Jid &jid) const 0561 { 0562 return d->jidCapabilitiesMap.contains( jid.full() ); 0563 } 0564 0565 XMPP::Features JabberCapabilitiesManager::features(const Jid& jid) const 0566 { 0567 QStringList featuresList; 0568 0569 if( capabilitiesEnabled(jid) ) 0570 { 0571 CapabilitiesList capabilitiesList = d->jidCapabilitiesMap[jid.full()].flatten(); 0572 0573 foreach(CapabilitiesList::value_type cap, capabilitiesList) 0574 { 0575 featuresList += d->capabilitiesInformationMap[cap].features(); 0576 } 0577 } 0578 0579 return Features(featuresList); 0580 } 0581 0582 QString JabberCapabilitiesManager::clientName(const Jid& jid) const 0583 { 0584 if( capabilitiesEnabled(jid) ) 0585 { 0586 Capabilities caps = d->jidCapabilitiesMap[jid.full()]; 0587 QString name = d->capabilitiesInformationMap[Capabilities(caps.node(),caps.version(),caps.version())].identities().first().name; 0588 0589 // Try to be intelligent about the name 0590 /*if (name.isEmpty()) { 0591 name = cs.node(); 0592 if (name.startsWith("http://")) 0593 name = name.right(name.length() - 7); 0594 0595 if (name.startsWith("www.")) 0596 name = name.right(name.length() - 4); 0597 0598 int cut_pos = name.find('.'); 0599 if (cut_pos != -1) { 0600 name = name.left(cut_pos); 0601 } 0602 }*/ 0603 0604 return name; 0605 } 0606 else 0607 { 0608 return QString(); 0609 } 0610 } 0611 0612 QString JabberCapabilitiesManager::clientVersion(const Jid& jid) const 0613 { 0614 return (capabilitiesEnabled(jid) ? d->jidCapabilitiesMap[jid.full()].version() : QString()); 0615 } 0616 0617 void JabberCapabilitiesManager::saveInformation() 0618 { 0619 QString capsFileName; 0620 capsFileName = KStandardDirs::locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); 0621 0622 // Generate XML 0623 QDomDocument doc; 0624 QDomElement capabilities = doc.createElement("capabilities"); 0625 doc.appendChild(capabilities); 0626 0627 QMap<Capabilities,CapabilitiesInformation>::ConstIterator it = d->capabilitiesInformationMap.constBegin(), itEnd = d->capabilitiesInformationMap.constEnd(); 0628 for( ; it != itEnd; ++it ) 0629 { 0630 QDomElement info = it.value().toXml(&doc); 0631 info.setAttribute("node",it.key().node()); 0632 info.setAttribute("ver",it.key().version()); 0633 info.setAttribute("ext",it.key().extensions()); 0634 capabilities.appendChild(info); 0635 } 0636 0637 // Save 0638 QFile capsFile(capsFileName); 0639 if( !capsFile.open(QIODevice::WriteOnly) ) 0640 { 0641 qCDebug(JABBER_PROTOCOL_LOG) << "Error while opening Capabilities cache file."; 0642 return; 0643 } 0644 0645 QTextStream textStream; 0646 textStream.setDevice(&capsFile); 0647 textStream.setCodec(QTextCodec::codecForName("UTF-8")); 0648 textStream << doc.toString(); 0649 textStream.setDevice(0); 0650 capsFile.close(); 0651 } 0652 0653 #include "moc_jabbercapabilitiesmanager.cpp"