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"