Merge branch '1.5'

This commit is contained in:
Linus Jahn 2023-03-17 17:24:52 +01:00
commit 55362b2e36
15 changed files with 401 additions and 130 deletions

View File

@ -49,6 +49,18 @@ jobs:
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install Qt
uses: jurplel/install-qt-action@v2
- name: Run tests
run: |
${env:PATH} += ";D:/a/qxmpp/qxmpp/src/Debug"
cmake . && cmake --build .
# ctest
# ctest --rerun-failed --output-on-failure
xmllint:
runs-on: ubuntu-latest
steps:

View File

@ -1659,7 +1659,7 @@ void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode
writer->writeStartElement(QStringLiteral("encryption"));
writer->writeDefaultNamespace(ns_eme);
writer->writeAttribute(QStringLiteral("namespace"), d->encryptionMethod);
helperToXmlAddAttribute(writer, QStringLiteral("name"), d->encryptionName);
helperToXmlAddAttribute(writer, QStringLiteral("name"), encryptionName());
writer->writeEndElement();
}

View File

@ -197,7 +197,7 @@ QXmppTask<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &writt
///
/// \since QXmpp 1.5
///
QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq, const QString &to)
{
using namespace QXmpp;
@ -212,7 +212,7 @@ QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
iq.setId(QXmppUtils::generateStanzaUuid());
}
return sendIq(QXmppPacket(iq), iq.id(), iq.to());
return sendIq(QXmppPacket(iq), iq.id(), to);
}
///
@ -466,10 +466,18 @@ bool QXmppStream::handleIqResponse(const QDomElement &stanza)
return false;
}
if (auto itr = d->runningIqs.find(stanza.attribute(QStringLiteral("id")));
const auto id = stanza.attribute(QStringLiteral("id"));
if (auto itr = d->runningIqs.find(id);
itr != d->runningIqs.end()) {
if (stanza.attribute("from") != itr.value().jid) {
warning(QStringLiteral("Received IQ response to one of our requests from wrong sender. Ignoring."));
const auto expectedFrom = itr.value().jid;
// Check that the sender of the response matches the recipient of the request.
// Stanzas coming from the server on behalf of the user's account must have no "from"
// attribute or have it set to the user's bare JID.
// If 'from' is empty, the IQ has been sent by the server. In this case we don't need to
// do the check as we trust the server anyways.
if (const auto from = stanza.attribute("from"); !from.isEmpty() && from != expectedFrom) {
warning(QStringLiteral("Ignored received IQ response to request '%1' because of wrong sender '%2' instead of expected sender '%3'")
.arg(id, from, expectedFrom));
return false;
}

View File

@ -47,7 +47,7 @@ public:
QXmppTask<QXmpp::SendResult> send(QXmppPacket &&);
using IqResult = std::variant<QDomElement, QXmppError>;
QXmppTask<IqResult> sendIq(QXmppIq &&);
QXmppTask<IqResult> sendIq(QXmppIq &&, const QString &to);
QXmppTask<IqResult> sendIq(QXmppPacket &&, const QString &id, const QString &to);
void cancelOngoingIqs();
bool hasIqId(const QString &id) const;

View File

@ -18,10 +18,9 @@ using namespace QXmpp::Private;
class CarbonEnableIq : public QXmppIq
{
public:
CarbonEnableIq(const QString &jid)
CarbonEnableIq()
: QXmppIq()
{
setTo(jid);
setType(QXmppIq::Set);
}
@ -165,7 +164,7 @@ void QXmppCarbonManagerV2::enableCarbons()
return;
}
client()->sendIq(CarbonEnableIq(client()->configuration().jidBare())).then(this, [this](QXmppClient::IqResult domResult) {
client()->sendIq(CarbonEnableIq()).then(this, [this](QXmppClient::IqResult domResult) {
if (auto err = parseIq(std::move(domResult))) {
warning("Could not enable message carbons: " % err->description);
} else {

View File

@ -95,12 +95,10 @@ namespace Private {
iq.parse(element);
iq.setE2eeMetadata(e2eeMetadata);
processHandleIqResult(
client,
iq.id(),
iq.from(),
e2eeMetadata,
invokeIqHandler(std::forward<Handler>(handler), std::move(iq)));
auto id = iq.id(), from = iq.from();
processHandleIqResult(client, id, from, e2eeMetadata,
invokeIqHandler(std::forward<Handler>(handler), std::move(iq)));
return true;
}
return false;

View File

@ -9,29 +9,98 @@
#include "QXmppConstants_p.h"
#include "QXmppDataForm.h"
#include "QXmppE2eeExtension.h"
#include "QXmppFutureUtils_p.h"
#include "QXmppMamIq.h"
#include "QXmppMessage.h"
#include "QXmppPromise.h"
#include "QXmppUtils.h"
#include <unordered_map>
#include <QDomElement>
using namespace QXmpp;
using namespace QXmpp::Private;
template<typename T, typename Converter>
auto transform(const T &input, Converter convert)
{
using Output = std::decay_t<decltype(convert(*input.begin()))>;
QVector<Output> output;
output.reserve(input.size());
std::transform(input.begin(), input.end(), std::back_inserter(output), std::move(convert));
return output;
}
template<typename T>
auto sum(const T &c)
{
return std::accumulate(c.begin(), c.end(), 0);
}
struct MamMessage
{
QDomElement element;
std::optional<QDateTime> delay;
};
enum EncryptedType { Unencrypted,
Encrypted };
QXmppMessage parseMamMessage(const MamMessage &mamMessage, EncryptedType encrypted)
{
QXmppMessage m;
m.parse(mamMessage.element, encrypted == Encrypted ? ScePublic : SceAll);
if (mamMessage.delay) {
m.setStamp(*mamMessage.delay);
}
return m;
}
std::optional<std::tuple<MamMessage, QString>> parseMamMessageResult(const QDomElement &messageEl)
{
auto resultElement = messageEl.firstChildElement("result");
if (resultElement.isNull() || resultElement.namespaceURI() != ns_mam) {
return {};
}
auto forwardedElement = resultElement.firstChildElement("forwarded");
if (forwardedElement.isNull() || forwardedElement.namespaceURI() != ns_forwarding) {
return {};
}
auto queryId = resultElement.attribute("queryid");
auto messageElement = forwardedElement.firstChildElement("message");
if (messageElement.isNull()) {
return {};
}
auto parseDelay = [](const auto &forwardedEl) -> std::optional<QDateTime> {
auto delayEl = forwardedEl.firstChildElement("delay");
if (!delayEl.isNull() && delayEl.namespaceURI() == ns_delayed_delivery) {
return QXmppUtils::datetimeFromString(delayEl.attribute("stamp"));
}
return {};
};
return { { MamMessage { messageElement, parseDelay(forwardedElement) }, queryId } };
}
struct RetrieveRequestState
{
QXmppPromise<QXmppMamManager::RetrieveResult> promise;
QXmppMamResultIq iq;
QVector<QXmppMessage> messages;
QVector<MamMessage> messages;
QVector<QXmppMessage> processedMessages;
uint runningDecryptionJobs = 0;
void finish()
{
Q_ASSERT(messages.count() == processedMessages.count());
promise.finish(
QXmppMamManager::RetrievedMessages {
std::move(iq),
std::move(messages) });
std::move(processedMessages) });
}
};
@ -87,28 +156,8 @@ QStringList QXmppMamManager::discoveryFeatures() const
bool QXmppMamManager::handleStanza(const QDomElement &element)
{
if (element.tagName() == "message") {
QDomElement resultElement = element.firstChildElement("result");
if (!resultElement.isNull() && resultElement.namespaceURI() == ns_mam) {
QDomElement forwardedElement = resultElement.firstChildElement("forwarded");
QString queryId = resultElement.attribute("queryid");
if (forwardedElement.isNull() || forwardedElement.namespaceURI() != ns_forwarding) {
return false;
}
auto messageElement = forwardedElement.firstChildElement("message");
auto delayElement = forwardedElement.firstChildElement("delay");
if (messageElement.isNull()) {
return false;
}
QXmppMessage message;
message.parse(messageElement);
if (!delayElement.isNull() && delayElement.namespaceURI() == ns_delayed_delivery) {
const QString stamp = delayElement.attribute("stamp");
message.setStamp(QXmppUtils::datetimeFromString(stamp));
}
if (auto result = parseMamMessageResult(element)) {
auto &[message, queryId] = *result;
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr != d->ongoingRequests.end()) {
@ -116,7 +165,7 @@ bool QXmppMamManager::handleStanza(const QDomElement &element)
itr->second.messages.append(std::move(message));
} else {
// signal-based API
Q_EMIT archivedMessageReceived(queryId, message);
Q_EMIT archivedMessageReceived(queryId, parseMamMessage(message, Unencrypted));
}
return true;
}
@ -240,74 +289,97 @@ QString QXmppMamManager::retrieveArchivedMessages(const QString &to,
QXmppTask<QXmppMamManager::RetrieveResult> QXmppMamManager::retrieveMessages(const QString &to, const QString &node, const QString &jid, const QDateTime &start, const QDateTime &end, const QXmppResultSetQuery &resultSetQuery)
{
auto queryIq = buildRequest(to, node, jid, start, end, resultSetQuery);
auto queryId = queryIq.queryId();
auto [itr, _] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() });
auto [itr, inserted] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() });
Q_ASSERT(inserted);
// create task here; promise could finish immediately after client()->sendIq()
auto task = itr->second.promise.task();
// retrieve messages
client()->sendIq(std::move(queryIq)).then(this, [this, queryId = queryIq.queryId()](QXmppClient::IqResult result) {
client()->sendIq(std::move(queryIq)).then(this, [this, queryId](QXmppClient::IqResult result) {
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr == d->ongoingRequests.end()) {
return;
}
auto &state = itr->second;
if (std::holds_alternative<QDomElement>(result)) {
auto &iq = itr->second.iq;
iq.parse(std::get<QDomElement>(result));
// handle IQ sending errors
if (std::holds_alternative<QXmppError>(result)) {
state.promise.finish(std::get<QXmppError>(result));
d->ongoingRequests.erase(itr);
return;
}
if (iq.type() == QXmppIq::Error) {
itr->second.promise.finish(QXmppError { iq.error().text(), iq.error() });
d->ongoingRequests.erase(itr);
// parse IQ
auto &iq = state.iq;
iq.parse(std::get<QDomElement>(result));
// handle MAM error result IQ
if (iq.type() == QXmppIq::Error) {
state.promise.finish(QXmppError { iq.error().text(), iq.error() });
d->ongoingRequests.erase(itr);
return;
}
// decrypt encrypted messages
if (auto *e2eeExt = client()->encryptionExtension()) {
// initialize processed messages (we need random access because
// decryptMessage() may finish in random order)
state.processedMessages.resize(state.messages.size());
// check for encrypted messages (once)
auto messagesEncrypted = transform(state.messages, [&](const auto &m) {
return e2eeExt->isEncrypted(m.element);
});
auto encryptedCount = sum(messagesEncrypted);
// We can't do this on the fly (with ++ and --) in the for loop
// because some decryptMessage() jobs could finish instantly
state.runningDecryptionJobs = encryptedCount;
for (auto i = 0; i < state.messages.size(); i++) {
if (!messagesEncrypted[i]) {
continue;
}
e2eeExt->decryptMessage(parseMamMessage(state.messages.at(i), Encrypted)).then(this, [this, i, queryId](auto result) {
auto itr = d->ongoingRequests.find(queryId.toStdString());
Q_ASSERT(itr != d->ongoingRequests.end());
auto &state = itr->second;
// store decrypted message, fallback to encrypted message
if (std::holds_alternative<QXmppMessage>(result)) {
state.processedMessages[i] = std::get<QXmppMessage>(std::move(result));
} else {
warning(QStringLiteral("Error decrypting message."));
state.processedMessages[i] = parseMamMessage(state.messages[i], Unencrypted);
}
// finish promise if this was the last job
state.runningDecryptionJobs--;
if (state.runningDecryptionJobs == 0) {
state.finish();
d->ongoingRequests.erase(itr);
}
});
}
// finishing the promise is done after decryptMessage()
if (encryptedCount > 0) {
return;
}
// decrypt encrypted messages
if (auto *e2eeExt = client()->encryptionExtension()) {
auto &messages = itr->second.messages;
auto running = std::make_shared<uint>(0);
// handle case when no message is encrypted
auto hasEncryptedMessages = false;
for (auto i = 0; i < messages.size(); i++) {
if (!e2eeExt->isEncrypted(messages.at(i))) {
continue;
}
hasEncryptedMessages = true;
auto message = messages.at(i);
(*running)++;
e2eeExt->decryptMessage(std::move(message)).then(this, [this, i, running, queryId](auto result) {
(*running)--;
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr == d->ongoingRequests.end()) {
return;
}
if (std::holds_alternative<QXmppMessage>(result)) {
itr->second.messages[i] = std::get<QXmppMessage>(std::move(result));
} else {
warning(QStringLiteral("Error decrypting message."));
}
if (*running == 0) {
itr->second.finish();
d->ongoingRequests.erase(itr);
}
});
}
if (!hasEncryptedMessages) {
// finish here, no decryptMessage callback will do it
itr->second.finish();
d->ongoingRequests.erase(itr);
}
} else {
itr->second.finish();
d->ongoingRequests.erase(itr);
}
} else {
itr->second.promise.finish(std::get<QXmppError>(result));
d->ongoingRequests.erase(itr);
}
// for the case without decryption, finish here
state.processedMessages = transform(state.messages, [](const auto &m) {
return parseMamMessage(m, Unencrypted);
});
state.finish();
d->ongoingRequests.erase(itr);
});
return itr->second.promise.task();
return task;
}

View File

@ -324,11 +324,9 @@ bool QXmppOutgoingClient::isStreamResumed() const
///
QXmppTask<QXmppStream::IqResult> QXmppOutgoingClient::sendIq(QXmppIq &&iq)
{
// always set a to address (the QXmppStream needs this for matching)
if (iq.to().isEmpty()) {
iq.setTo(d->config.domain());
}
return QXmppStream::sendIq(std::move(iq));
// If 'to' is empty the user's bare JID is meant implicitly (see RFC6120, section 10.3.3.).
auto to = iq.to();
return QXmppStream::sendIq(std::move(iq), to.isEmpty() ? d->config.jidBare() : to);
}
void QXmppOutgoingClient::_q_socketDisconnected()

View File

@ -340,7 +340,7 @@ QXmppOmemoManager::~QXmppOmemoManager() = default;
///
/// This should be called after starting the client and before the login.
/// It must only be called after \c setUp() has been called once for the user
/// during one of the past login session.
/// during one of the past login sessions.
/// It does not need to be called if setUp() has been called during the current
/// login session.
///
@ -1267,29 +1267,33 @@ bool Manager::handlePubSubEvent(const QDomElement &element, const QString &pubSu
switch (event.eventType()) {
// Items have been published.
case QXmppPubSubEventBase::Items: {
const auto items = event.items();
// Only process items if the event notification contains one.
// That is necessary because PubSub allows publishing without
// items leading to notification-only events.
if (!items.isEmpty()) {
const auto &deviceListItem = items.constFirst();
if (deviceListItem.id() == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
d->updateDevices(pubSubService, event.items().constFirst());
// That is necessary because PubSub allows publishing without items leading to
// notification-only events.
if (const auto &items = event.items(); !items.isEmpty()) {
// Since the usage of the item ID \c QXmppPubSubManager::Current is only RECOMMENDED
// by \xep{0060, Publish-Subscribe} (PubSub) but not obligatory, an appropriate
// contact device list is determined.
// In case of the own device list node, it is sctrictly processed as a recommended
// singleton item and changed to fit that if needed.
const auto isOwnDeviceListNode = d->ownBareJid() == pubSubService;
if (isOwnDeviceListNode) {
const auto &deviceListItem = items.constFirst();
if (deviceListItem.id() == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
d->updateDevices(pubSubService, event.items().constFirst());
} else {
d->handleIrregularDeviceListChanges(pubSubService);
}
} else {
d->handleIrregularDeviceListChanges(pubSubService);
d->updateContactDevices(pubSubService, items);
}
}
break;
}
// Items have been retracted.
// Specific items are deleted.
case QXmppPubSubEventBase::Retract: {
// Specific items are deleted.
const auto &retractedItem = event.retractIds().constFirst();
if (retractedItem == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
d->handleIrregularDeviceListChanges(pubSubService);
}
d->handleIrregularDeviceListChanges(pubSubService);
}
// All items are deleted.
case QXmppPubSubEventBase::Purge:

View File

@ -920,7 +920,7 @@ bool ManagerPrivate::updatePreKeyPairs(uint32_t count)
deviceBundle.addPublicPreKey(preKeyId, serializedPublicPreKey);
}
this->preKeyPairs.insert(serializedPreKeyPairs);
preKeyPairs.insert(serializedPreKeyPairs);
omemoStorage->addPreKeyPairs(serializedPreKeyPairs);
ownDevice.latestPreKeyId = latestPreKeyId - 1 + count;
@ -2647,6 +2647,36 @@ void ManagerPrivate::updateOwnDevicesLocally(bool isDeviceListNodeExistent, Func
}
}
//
// Updates all locally stored devices of a contact.
//
// \param deviceOwnerJid bare JID of the devices' owner
// \param deviceListItems PEP items that may contain a device list
//
// \returns a found device list item
//
std::optional<QXmppOmemoDeviceListItem> QXmppOmemoManagerPrivate::updateContactDevices(const QString &deviceOwnerJid, const QVector<QXmppOmemoDeviceListItem> &deviceListItems)
{
if (deviceListItems.size() > 1) {
const auto itr = std::find_if(deviceListItems.cbegin(), deviceListItems.cend(), [=](const QXmppOmemoDeviceListItem &item) {
return item.id() == QXmppPubSubManager::Current;
});
if (itr != deviceListItems.cend()) {
updateDevices(deviceOwnerJid, *itr);
return *itr;
} else {
warning("Device list for JID '" % deviceOwnerJid % "' could not be updated because the node contains more than one item but none with the singleton node's specific ID '" % QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current) % "'");
handleIrregularDeviceListChanges(deviceOwnerJid);
return {};
}
}
const auto &item = deviceListItems.constFirst();
updateDevices(deviceOwnerJid, item);
return item;
}
//
// Updates all locally stored devices by a passed device list item.
//
@ -2783,7 +2813,7 @@ void ManagerPrivate::updateDevices(const QString &deviceOwnerJid, const QXmppOme
// Publish an own correct device list if the PEP service's one is incorrect
// and the devices are already set up locally.
if (isOwnDeviceListIncorrect) {
if (!this->devices.isEmpty()) {
if (!devices.isEmpty()) {
publishDeviceListItem(true, [=](bool isPublished) {
if (!isPublished) {
warning("Own device list item could not be published in order to correct the PEP service's one");
@ -2795,7 +2825,7 @@ void ManagerPrivate::updateDevices(const QString &deviceOwnerJid, const QXmppOme
//
// Corrects the own device list on the PEP service by the locally stored
// devices or set a contact device to be removed locally in the future.
// devices or sets a contact device to be removed locally in the future.
//
// \param deviceOwnerJid bare JID of the devices' owner
//
@ -2810,7 +2840,7 @@ void ManagerPrivate::handleIrregularDeviceListChanges(const QString &deviceOwner
auto future = pubSubManager->deleteOwnPepNode(ns_omemo_2_devices);
future.then(q, [=](QXmppPubSubManager::Result result) {
if (const auto error = std::get_if<QXmppError>(&result)) {
warning("Node '" % QString(ns_omemo_2_devices) % "' of JID '" % deviceOwnerJid %
warning("Node '" % QString(ns_omemo_2_devices) % "' of JID '" % deviceOwnerJid %
"' could not be deleted in order to recover from an inconsistent node: " %
errorToString(*error));
} else {
@ -2845,7 +2875,7 @@ void ManagerPrivate::handleIrregularDeviceListChanges(const QString &deviceOwner
}
});
} else {
auto &ownerDevices = this->devices[deviceOwnerJid];
auto &ownerDevices = devices[deviceOwnerJid];
// Set a timestamp for locally stored contact devices being removed
// later if their device list item is removed, if their device list node
@ -3055,16 +3085,27 @@ QXmppTask<bool> ManagerPrivate::changeDeviceLabel(const QString &deviceLabel)
//
QXmppTask<QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem>> ManagerPrivate::requestDeviceList(const QString &jid)
{
auto future = pubSubManager->requestItem<QXmppOmemoDeviceListItem>(jid, ns_omemo_2_devices, QXmppPubSubManager::Current);
future.then(q, [this, jid](QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem> result) mutable {
QXmppPromise<QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem>> interface;
// Since the usage of the item ID \c QXmppPubSubManager::Current is only RECOMMENDED by
// \xep{0060, Publish-Subscribe} (PubSub) but not obligatory, all items are requested even if
// the node should contain only one item.
auto future = pubSubManager->requestItems<QXmppOmemoDeviceListItem>(jid, ns_omemo_2_devices);
future.then(q, [this, interface, jid](QXmppPubSubManager::ItemsResult<QXmppOmemoDeviceListItem> result) mutable {
if (const auto error = std::get_if<QXmppError>(&result)) {
warning("Device list for JID '" % jid % "' could not be retrieved: " % errorToString(*error));
interface.finish(*error);
} else if (const auto &items = std::get<QXmppPubSubManager::Items<QXmppOmemoDeviceListItem>>(result).items; items.isEmpty()) {
const auto errorMessage = "Device list for JID '" % jid % "' could not be retrieved because the node does not contain any item";
warning(errorMessage);
interface.finish(QXmppError { errorMessage });
} else if (const auto item = updateContactDevices(jid, items); item) {
interface.finish(*item);
} else {
const auto &item = std::get<QXmppOmemoDeviceListItem>(result);
updateDevices(jid, item);
interface.finish(QXmppError { "Device list for JID '" % jid % "' could not be retrieved because the node does not contain an appropriate item" });
}
});
return future;
return interface.task();
}
//

View File

@ -290,6 +290,7 @@ public:
QXmppOmemoDeviceListItem deviceListItem(bool addOwnDevice = true);
template<typename Function>
void updateOwnDevicesLocally(bool isDeviceListNodeExistent, Function continuation);
std::optional<QXmppOmemoDeviceListItem> updateContactDevices(const QString &deviceOwnerJid, const QVector<QXmppOmemoDeviceListItem> &deviceListItems);
void updateDevices(const QString &deviceOwnerJid, const QXmppOmemoDeviceListItem &deviceListItem);
void handleIrregularDeviceListChanges(const QString &deviceOwnerJid);
template<typename Function>

View File

@ -35,6 +35,7 @@ add_simple_test(qxmppdataform)
add_simple_test(qxmppdiscoveryiq)
add_simple_test(qxmppdiscoverymanager TestClient.h)
add_simple_test(qxmppentitytimeiq)
add_simple_test(qxmppentitytimemanager TestClient.h)
add_simple_test(qxmppexternalservicediscoveryiq)
add_simple_test(qxmppexternalservicediscoverymanager TestClient.h)
add_simple_test(qxmpphttpuploadiq)
@ -79,6 +80,7 @@ add_simple_test(qxmppusertunemanager TestClient.h)
add_simple_test(qxmppvcardiq)
add_simple_test(qxmppvcardmanager)
add_simple_test(qxmppversioniq)
add_simple_test(qxmppversionmanager TestClient.h)
if(WITH_QCA)
add_simple_test(qxmppfileencryption)

View File

@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "QXmppEntityTimeIq.h"
#include "QXmppEntityTimeManager.h"
#include "TestClient.h"
Q_DECLARE_METATYPE(QXmppEntityTimeIq)
class tst_QXmppEntityTimeManager : public QObject
{
Q_OBJECT
Q_SLOT void initTestCase();
Q_SLOT void testSendRequest();
Q_SLOT void testHandleRequest();
};
void tst_QXmppEntityTimeManager::initTestCase()
{
qRegisterMetaType<QXmppEntityTimeIq>();
}
void tst_QXmppEntityTimeManager::testSendRequest()
{
TestClient test;
auto *manager = test.addNewExtension<QXmppEntityTimeManager>();
QSignalSpy spy(manager, &QXmppEntityTimeManager::timeReceived);
manager->requestTime("juliet@capulet.com/balcony");
test.expect("<iq id='qxmpp1' to='juliet@capulet.com/balcony' type='get'><time xmlns='urn:xmpp:time'/></iq>");
manager->handleStanza(xmlToDom(R"(<iq id='qxmpp1' to='romeo@montague.net/orchard' from='juliet@capulet.com/balcony' type='result'>
<time xmlns='urn:xmpp:time'>
<tzo>-06:00</tzo>
<utc>2006-12-19T17:58:35Z</utc>
</time>
</iq>)"));
QCOMPARE(spy.size(), 1);
auto time = spy.at(0).at(0).value<QXmppEntityTimeIq>();
QCOMPARE(time.utc(), QDateTime({2006, 12, 19}, {17, 58, 35}, Qt::UTC));
QCOMPARE(time.tzo(), -6 * 60 * 60);
}
void tst_QXmppEntityTimeManager::testHandleRequest()
{
TestClient test;
test.configuration().setJid("juliet@capulet.com/balcony");
auto *manager = test.addNewExtension<QXmppEntityTimeManager>();
manager->handleStanza(xmlToDom(R"(<iq type='get' from='romeo@montague.net/orchard' to='juliet@capulet.com/balcony' id='time_1'>
<time xmlns='urn:xmpp:time'/>
</iq>)"));
auto packet = xmlToDom(test.takePacket());
QVERIFY(QXmppEntityTimeIq::isEntityTimeIq(packet));
QXmppEntityTimeIq resp;
resp.parse(packet);
QCOMPARE(resp.id(), QStringLiteral("time_1"));
QCOMPARE(resp.type(), QXmppIq::Result);
}
QTEST_MAIN(tst_QXmppEntityTimeManager)
#include "tst_qxmppentitytimemanager.moc"

View File

@ -783,7 +783,7 @@ void tst_QXmppMessage::testEme()
// test standard encryption: OMEMO
const QByteArray xmlOmemo(
"<message to=\"foo@example.com/QXmpp\" from=\"bar@example.com/QXmpp\" type=\"normal\">"
"<encryption xmlns=\"urn:xmpp:eme:0\" namespace=\"eu.siacs.conversations.axolotl\"/>"
"<encryption xmlns=\"urn:xmpp:eme:0\" namespace=\"eu.siacs.conversations.axolotl\" name=\"OMEMO\"/>"
"<body>This message is encrypted with OMEMO, but your client doesn&apos;t seem to support that.</body>"
"</message>");

View File

@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "QXmppVersionManager.h"
#include "QXmppVersionIq.h"
#include "TestClient.h"
Q_DECLARE_METATYPE(QXmppVersionIq);
class tst_QXmppVersionManager : public QObject
{
Q_OBJECT
Q_SLOT void initTestCase();
Q_SLOT void testSendRequest();
Q_SLOT void testHandleRequest();
};
void tst_QXmppVersionManager::initTestCase()
{
qRegisterMetaType<QXmppVersionIq>();
}
void tst_QXmppVersionManager::testSendRequest()
{
TestClient test;
auto *verManager = test.addNewExtension<QXmppVersionManager>();
QSignalSpy spy(verManager, &QXmppVersionManager::versionReceived);
auto id = verManager->requestVersion("juliet@capulet.com/balcony");
test.expect("<iq id='qxmpp1' to='juliet@capulet.com/balcony' type='get'><query xmlns='jabber:iq:version'/></iq>");
verManager->handleStanza(xmlToDom(R"(<iq type='result' from='juliet@capulet.com/balcony' id='qxmpp1'>
<query xmlns='jabber:iq:version'>
<name>Exodus</name>
<version>0.7.0.4</version>
<os>Windows-XP 5.01.2600</os>
</query>
</iq>)"));
QCOMPARE(spy.size(), 1);
auto version = spy.at(0).at(0).value<QXmppVersionIq>();
QCOMPARE(version.name(), QStringLiteral("Exodus"));
QCOMPARE(version.version(), QStringLiteral("0.7.0.4"));
QCOMPARE(version.os(), QStringLiteral("Windows-XP 5.01.2600"));
}
void tst_QXmppVersionManager::testHandleRequest()
{
TestClient test;
test.configuration().setJid("juliet@capulet.com/balcony");
auto *verManager = test.addNewExtension<QXmppVersionManager>();
verManager->setClientName("Exodus");
verManager->setClientVersion("0.7.0.4");
verManager->setClientOs("Windows-XP 5.01.2600");
verManager->handleStanza(xmlToDom(R"(<iq type='get' from='romeo@montague.net/orchard' to='juliet@capulet.com/balcony' id='version_1'>
<query xmlns='jabber:iq:version'/>
</iq>)"));
test.expect(R"(<iq id='version_1' to='romeo@montague.net/orchard' type='result'>)"
"<query xmlns='jabber:iq:version'><name>Exodus</name><os>Windows-XP 5.01.2600</os><version>0.7.0.4</version>"
"</query></iq>");
}
QTEST_MAIN(tst_QXmppVersionManager)
#include "tst_qxmppversionmanager.moc"