1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
|
// SPDX-FileCopyrightText: 2016 Niels Ole Salscheider <niels_ole@salscheider-online.de>
// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "QXmppMamManager.h"
#include "QXmppClient.h"
#include "QXmppConstants_p.h"
#include "QXmppDataForm.h"
#include "QXmppE2eeExtension.h"
#include "QXmppFutureUtils_p.h"
#include "QXmppMamIq.h"
#include "QXmppMessage.h"
#include "QXmppUtils.h"
#include <unordered_map>
#include <QDomElement>
using namespace QXmpp::Private;
struct RetrieveRequestState
{
QXmppPromise<QXmppMamManager::RetrieveResult> promise;
QXmppMamResultIq iq;
QVector<QXmppMessage> messages;
void finish()
{
promise.finish(
QXmppMamManager::RetrievedMessages {
std::move(iq),
std::move(messages) });
}
};
class QXmppMamManagerPrivate
{
public:
// std::string because older Qt 5 versions don't add std::hash support for QString
std::unordered_map<std::string, RetrieveRequestState> ongoingRequests;
};
///
/// \struct QXmppMamManager::RetrievedMessages
///
/// \brief Contains all retrieved messages and the result IQ that can be used for pagination.
///
/// \since QXmpp 1.5
///
///
/// \var QXmppMamManager::RetrievedMessages::result
///
/// The returned result IQ from the MAM server.
///
///
/// \var QXmppMamManager::RetrievedMessages::messages
///
/// A vector of retrieved QXmppMessages.
///
///
/// \typedef QXmppMamManager::RetrieveResult
///
/// Contains RetrievedMessages or an QXmppError.
///
/// \since QXmpp 1.5
///
QXmppMamManager::QXmppMamManager()
: d(std::make_unique<QXmppMamManagerPrivate>())
{
}
QXmppMamManager::~QXmppMamManager() = default;
/// \cond
QStringList QXmppMamManager::discoveryFeatures() const
{
// XEP-0313: Message Archive Management
return QStringList() << ns_mam;
}
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));
}
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr != d->ongoingRequests.end()) {
// future-based API
itr->second.messages.append(std::move(message));
} else {
// signal-based API
Q_EMIT archivedMessageReceived(queryId, message);
}
return true;
}
} else if (QXmppMamResultIq::isMamResultIq(element)) {
QXmppMamResultIq result;
result.parse(element);
Q_EMIT resultsRecieved(result.id(), result.resultSetReply(), result.complete());
return true;
}
return false;
}
/// \endcond
static QXmppMamQueryIq buildRequest(const QString &to,
const QString &node,
const QString &jid,
const QDateTime &start,
const QDateTime &end,
const QXmppResultSetQuery &resultSetQuery)
{
QList<QXmppDataForm::Field> fields;
QXmppDataForm::Field hiddenField(QXmppDataForm::Field::HiddenField);
hiddenField.setKey("FORM_TYPE");
hiddenField.setValue(ns_mam);
fields << hiddenField;
if (!jid.isEmpty()) {
QXmppDataForm::Field jidField;
jidField.setKey("with");
jidField.setValue(jid);
fields << jidField;
}
if (start.isValid()) {
QXmppDataForm::Field startField;
startField.setKey("start");
startField.setValue(QXmppUtils::datetimeToString(start));
fields << startField;
}
if (end.isValid()) {
QXmppDataForm::Field endField;
endField.setKey("end");
endField.setValue(QXmppUtils::datetimeToString(end));
fields << endField;
}
QXmppDataForm form;
form.setType(QXmppDataForm::Submit);
form.setFields(fields);
QXmppMamQueryIq queryIq;
QString queryId = queryIq.id(); /* reuse the IQ id as query id */
queryIq.setTo(to);
queryIq.setNode(node);
queryIq.setQueryId(queryId);
queryIq.setForm(form);
queryIq.setResultSetQuery(resultSetQuery);
return queryIq;
}
///
/// Retrieves archived messages. For each received message, the
/// archiveMessageReceived() signal is emitted. Once all messages are received,
/// the resultsRecieved() signal is emitted. It returns a result set that can
/// be used to page through the results.
/// The number of results may be limited by the server.
///
/// \warning This API does not work with end-to-end encrypted messages. You can
/// use the new QFuture-based API (retrieveMessages()) for that.
///
/// \param to Optional entity that should be queried. Leave this empty to query
/// the local archive.
/// \param node Optional node that should be queried. This is used when querying
/// a pubsub node.
/// \param jid Optional JID to filter the results.
/// \param start Optional start time to filter the results.
/// \param end Optional end time to filter the results.
/// \param resultSetQuery Optional Result Set Management query. This can be used
/// to limit the number of results and to page through the
/// archive.
/// \return query id of the request. This can be used to associate the
/// corresponding resultsRecieved signal.
///
QString QXmppMamManager::retrieveArchivedMessages(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);
client()->sendPacket(queryIq);
return queryIq.id();
}
///
/// Retrieves archived messages and reports all messages at once via a QFuture.
///
/// This function tries to decrypt encrypted messages.
///
/// The number of results may be limited by the server.
///
/// \param to Optional entity that should be queried. Leave this empty to query
/// the local archive.
/// \param node Optional node that should be queried. This is used when querying
/// a pubsub node.
/// \param jid Optional JID to filter the results.
/// \param start Optional start time to filter the results.
/// \param end Optional end time to filter the results.
/// \param resultSetQuery Optional Result Set Management query. This can be used
/// to limit the number of results and to page through the
/// archive.
/// \return query id of the request. This can be used to associate the
/// corresponding resultsRecieved signal.
///
/// \since QXmpp 1.5
///
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 [itr, _] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() });
// retrieve messages
client()->sendIq(std::move(queryIq)).then(this, [this, queryId = queryIq.queryId()](QXmppClient::IqResult result) {
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr == d->ongoingRequests.end()) {
return;
}
if (std::holds_alternative<QDomElement>(result)) {
auto &iq = itr->second.iq;
iq.parse(std::get<QDomElement>(result));
if (iq.type() == QXmppIq::Error) {
itr->second.promise.finish(QXmppError { iq.error().text(), iq.error() });
d->ongoingRequests.erase(itr);
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);
}
});
return itr->second.promise.task();
}
|