diff --git a/core/account.cpp b/core/account.cpp index 08321d3..48c48f9 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -634,16 +634,16 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) QString node = info.queryNode(); if (!node.isEmpty()) { QStringList feats = info.features(); - std::list identities; + std::set identities; std::set features(feats.begin(), feats.end()); QList idents = info.identities(); for (const QXmppDiscoveryIq::Identity& ident : idents) { - identities.emplace_back(); - Shared::Identity& identity = identities.back(); + Shared::Identity identity; identity.category = ident.category(); identity.language = ident.language(); identity.name = ident.name(); identity.type = ident.type(); + identities.insert(identity); qDebug() << " " << identity.name << identity.category << identity.type; } @@ -655,6 +655,16 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) } } +void Core::Account::discoverInfo(const QString& address, const QString& node) { + if (state == Shared::ConnectionState::connected) { + dm->requestInfo(address, node); + } else { + qDebug() << "An attempt to send a discover info by account" << name << + "sending request to" << address << "about node" << node << + "but the account is not in the connected state, skipping"; + } +} + void Core::Account::handleDisconnection() { cm->setCarbonsEnabled(false); diff --git a/core/account.h b/core/account.h index 5651ad9..a409490 100644 --- a/core/account.h +++ b/core/account.h @@ -155,7 +155,7 @@ signals: void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap headers); void uploadFileError(const QString& jid, const QString& messageId, const QString& error); void needPassword(); - void infoDiscovered(const QString& address, const QString& node, const std::list& identities, const std::set& features); + void infoDiscovered(const QString& address, const QString& node, const std::set& identities, const std::set& features); private: QString name; diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp index 0259f9c..a054428 100644 --- a/core/components/clientcache.cpp +++ b/core/components/clientcache.cpp @@ -14,12 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - #include "clientcache.h" +#include + Core::ClientCache::ClientCache(): requested(), - cache("clients") + cache("clients"), + specific() { cache.open(); } @@ -39,9 +41,9 @@ bool Core::ClientCache::checkClient(const QString& node, const QString& ver, con QString id = node + "/" + ver; if (requested.count(id) == 0 && !cache.checkRecord(id)) { Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second; - info.capabilitiesNode = node; - info.capabilitiesVerification = ver; - info.capabilitiesHash = hash; + info.node = node; + info.verification = ver; + info.hash = hash; emit requestClientInfo(id); return false; } @@ -49,7 +51,28 @@ bool Core::ClientCache::checkClient(const QString& node, const QString& ver, con return true; } +bool Core::ClientCache::registerClientInfo ( + const QString& sourceFullJid, + const QString& id, + const std::set& identities, + const std::set& features) +{ + std::map::iterator itr = requested.find(id); + if (itr != requested.end()) { + Shared::ClientInfo& info = itr->second; + info.identities = identities; + info.extensions = features; -bool Core::ClientCache::registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set& features) { - + bool valid = info.valid(); + if (valid) { + cache.addRecord(id, info); + } else { + info.specificPresence = sourceFullJid; + specific.insert(std::make_pair(sourceFullJid, info)); + } + requested.erase(id); + return valid; + } else { + return false; + } } diff --git a/core/components/clientcache.h b/core/components/clientcache.h index 78c7e84..b2ff70d 100644 --- a/core/components/clientcache.h +++ b/core/components/clientcache.h @@ -43,11 +43,12 @@ signals: public slots: bool checkClient(const QString& node, const QString& ver, const QString& hash); - bool registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set& features); + bool registerClientInfo(const QString& sourceFullJid, const QString& id, const std::set& identities, const std::set& features); private: std::map requested; Cache cache; + std::map specific; }; } diff --git a/core/squawk.cpp b/core/squawk.cpp index 5edcf58..3b976b0 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -320,25 +320,27 @@ void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, Account* acc = static_cast(sender()); emit addPresence(acc->getName(), jid, name, data); - QString node = data["capabilityNode"].toString(); - QString ver = data["capabilityVer"].toString(); - QString hash = data["capabilityHash"].toString(); - if (!clientCache.checkClient(node, ver, hash)) { - acc->discoverInfo(jid + "/" + name, node + "/" + ver); + //it's equal if a MUC sends its status with presence of the same jid (ex: muc@srv.im/muc@srv.im), it's not a client, so, no need to request + if (jid != name) { + QString node = data["capabilityNode"].toString(); + QString ver = data["capabilityVer"].toString(); + QString hash = data["capabilityHash"].toString(); + if (!clientCache.checkClient(node, ver, hash)) { + acc->discoverInfo(jid + "/" + name, node + "/" + ver); + } } } void Core::Squawk::onAccountInfoDiscovered( const QString& address, const QString& node, - const std::list& identities, + const std::set& identities, const std::set& features) { Account* acc = static_cast(sender()); - if (identities.size() != 1 || clientCache.registerClientInfo(address, node, identities.back(), features)) { + if (!clientCache.registerClientInfo(address, node, identities, features)) { qDebug() << "Account" << acc->getName() << "received an ill-formed client discovery response from" << address << "about" << node; - return; } } diff --git a/core/squawk.h b/core/squawk.h index ea17cdf..cdaaf6e 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -183,7 +183,7 @@ private slots: void onWalletOpened(bool success); void onWalletRejectPassword(const QString& login); - void onAccountInfoDiscovered(const QString& address, const QString& node, const std::list& identities, const std::set& features); + void onAccountInfoDiscovered(const QString& address, const QString& node, const std::set& identities, const std::set& features); private: void readSettings(); diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index 5828bd4..7495d4e 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -21,4 +21,5 @@ target_sources(squawk PRIVATE clientinfo.h clientinfo.cpp identity.h + identity.cpp ) diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp index 3a2f441..86af1c8 100644 --- a/shared/clientinfo.cpp +++ b/shared/clientinfo.cpp @@ -16,30 +16,42 @@ #include "clientinfo.h" +const std::map Shared::ClientInfo::hashes = { + //md2 is missing + {"md5", QCryptographicHash::Md5}, + {"sha-1", QCryptographicHash::Sha1}, + {"sha-224", QCryptographicHash::Sha224}, + {"sha-256", QCryptographicHash::Sha256}, + {"sha-384", QCryptographicHash::Sha384}, + {"sha-512", QCryptographicHash::Sha512}, + //shake128 is missing + //shake256 is missing + +}; + Shared::ClientInfo::ClientInfo(): - name(), - category(), - type(), - capabilitiesNode(), - capabilitiesVerification(), - capabilitiesHash(), - specificPresence(), - capabilitiesExtensions() {} + identities(), + extensions(), + node(), + verification(), + hash(), + specificPresence() {} QString Shared::ClientInfo::getId() const { - return capabilitiesNode + "/" + capabilitiesVerification; + return node + "/" + verification; } QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const { - stream << name; - stream << category; - stream << type; - stream << capabilitiesNode; - stream << capabilitiesVerification; - stream << capabilitiesHash; - stream << (quint8)capabilitiesExtensions.size(); - for (const QString& ext : capabilitiesExtensions) { + stream << node; + stream << verification; + stream << hash; + stream << (quint8)identities.size(); + for (const Shared::Identity& identity : identities) { + stream << identity; + } + stream << (quint8)extensions.size(); + for (const QString& ext : extensions) { stream << ext; } @@ -47,29 +59,53 @@ QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const { } QDataStream & Shared::ClientInfo::operator << (QDataStream& stream) { - stream >> name; - stream >> category; - stream >> type; - stream >> capabilitiesNode; - stream >> capabilitiesVerification; - stream >> capabilitiesHash; + stream >> node; + stream >> verification; + stream >> hash; quint8 size; + stream >> size; + for (quint8 i = 0; i < size; ++i) { + Shared::Identity identity; + stream >> identity; + identities.insert(identity); + } + stream >> size; for (quint8 i = 0; i < size; ++i) { QString ext; stream >> ext; - capabilitiesExtensions.insert(ext); + extensions.insert(ext); } return stream; } -QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image) { - image >> stream; +bool Shared::ClientInfo::valid() const { + std::map::const_iterator itr = hashes.find(hash); + if (itr == hashes.end()) { + return false; + } + + QCryptographicHash calc(itr->second); + QString validationString = ""; + for (const Identity& identity : identities) { + calc.addData((identity.category + "/" + identity.type + "/" + identity.language + "/" + identity.name + "<").toUtf8()); + } + for (const QString& ext : extensions) { + calc.addData((ext + "<").toUtf8()); + } + + QString result = calc.result().toBase64(); + + return result == verification; +} + +QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info) { + info >> stream; return stream; } -QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image) { - image << stream; +QDataStream& operator >> (QDataStream& stream, Shared::ClientInfo& info) { + info << stream; return stream; } diff --git a/shared/clientinfo.h b/shared/clientinfo.h index 55dd29f..53c7dd0 100644 --- a/shared/clientinfo.h +++ b/shared/clientinfo.h @@ -21,6 +21,9 @@ #include #include +#include + +#include namespace Shared { @@ -30,24 +33,26 @@ public: ClientInfo(); QString getId() const; + bool valid() const; QDataStream& operator << (QDataStream& stream); QDataStream& operator >> (QDataStream& stream) const; public: - QString name; - QString category; - QString type; - QString capabilitiesNode; - QString capabilitiesVerification; - QString capabilitiesHash; + std::set identities; + std::set extensions; + QString node; + QString verification; + QString hash; QString specificPresence; - std::set capabilitiesExtensions; + +private: + static const std::map hashes; }; } -QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image); -QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image); +QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info); +QDataStream& operator >> (QDataStream& stream, Shared::ClientInfo& info); #endif // SHARED_CLIENTINFO_H diff --git a/shared/identity.cpp b/shared/identity.cpp new file mode 100644 index 0000000..a0a4f0a --- /dev/null +++ b/shared/identity.cpp @@ -0,0 +1,142 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "identity.h" + +Shared::Identity::Identity(): + category(), + type(), + language(), + name() {} + +bool Shared::Identity::operator==(const Shared::Identity& other) const { + return category == other.category && type == other.type && language == other.language && name == other.name; +} + +bool Shared::Identity::operator!=(const Shared::Identity& other) const { + return category != other.category || type != other.type || language != other.language || name != other.name; +} + +bool Shared::Identity::operator > (const Shared::Identity& other) const { + if (category > other.category) { + return true; + } else if (category < other.category) { + return false; + } else if (type > other.type) { + return true; + } else if (type < other.type) { + return false; + } else if (language > other.language) { + return true; + } else if (language < other.language) { + return false; + } else if (name > other.name) { + return true; + } else { + return false; + } +} + +bool Shared::Identity::operator < (const Shared::Identity& other) const { + if (category < other.category) { + return true; + } else if (category > other.category) { + return false; + } else if (type < other.type) { + return true; + } else if (type > other.type) { + return false; + } else if (language < other.language) { + return true; + } else if (language > other.language) { + return false; + } else if (name < other.name) { + return true; + } else { + return false; + } +} + +bool Shared::Identity::operator >= (const Shared::Identity& other) const { + if (category > other.category) { + return true; + } else if (category < other.category) { + return false; + } else if (type > other.type) { + return true; + } else if (type < other.type) { + return false; + } else if (language > other.language) { + return true; + } else if (language < other.language) { + return false; + } else if (name > other.name) { + return true; + } else if (name < other.name) { + return false; + } else { + return true; + } +} + +bool Shared::Identity::operator <= (const Shared::Identity& other) const { + if (category < other.category) { + return true; + } else if (category > other.category) { + return false; + } else if (type < other.type) { + return true; + } else if (type > other.type) { + return false; + } else if (language < other.language) { + return true; + } else if (language > other.language) { + return false; + } else if (name < other.name) { + return true; + } else if (name > other.name) { + return false; + } else { + return true; + } +} + +QDataStream & Shared::Identity::operator >> (QDataStream& stream) const { + stream << category; + stream << type; + stream << language; + stream << name; +} + +QDataStream & Shared::Identity::operator << (QDataStream& stream) { + stream >> category; + stream >> type; + stream >> language; + stream >> name; +} + +QDataStream & operator >> (QDataStream& stream, Shared::Identity& identity) { + identity << stream; + return stream; +} + +QDataStream & operator << (QDataStream& stream, const Shared::Identity& identity) { + identity >> stream; + return stream; +} + diff --git a/shared/identity.h b/shared/identity.h index 6041e1a..c19102b 100644 --- a/shared/identity.h +++ b/shared/identity.h @@ -19,17 +19,34 @@ #ifndef SHARED_IDENTITY_H #define SHARED_IDENTITY_H +#include #include namespace Shared { -struct Identity { +class Identity { +public: + Identity(); + + QDataStream& operator << (QDataStream& stream); + QDataStream& operator >> (QDataStream& stream) const; + + bool operator < (const Identity& other) const; + bool operator > (const Identity& other) const; + bool operator >= (const Identity& other) const; + bool operator <= (const Identity& other) const; + bool operator == (const Identity& other) const; + bool operator != (const Identity& other) const; +public: QString category; + QString type; QString language; QString name; - QString type; }; } +QDataStream& operator << (QDataStream& stream, const Shared::Identity& identity); +QDataStream& operator >> (QDataStream& stream, Shared::Identity& identity); + #endif //SHARED_IDENTITY_H