diff --git a/.gitignore b/.gitignore index fbb23911..ceb5082b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,6 @@ object_script.* /bin /lib /plugins +*.user* build-* -*.pro.user* 3rdparty/qca diff --git a/include/iris/xmpp_carbons.h b/include/iris/xmpp_carbons.h new file mode 100644 index 00000000..444f2293 --- /dev/null +++ b/include/iris/xmpp_carbons.h @@ -0,0 +1 @@ +#include "../../src/xmpp/xmpp-im/xmpp_carbons.h" diff --git a/include/iris/xmpp_forwarding.h b/include/iris/xmpp_forwarding.h new file mode 100644 index 00000000..6225ec94 --- /dev/null +++ b/include/iris/xmpp_forwarding.h @@ -0,0 +1 @@ +#include "../../src/xmpp/xmpp-im/xmpp_forwarding.h" diff --git a/include/iris/xmpp_mammanager.h b/include/iris/xmpp_mammanager.h new file mode 100644 index 00000000..15450e52 --- /dev/null +++ b/include/iris/xmpp_mammanager.h @@ -0,0 +1 @@ +#include "xmpp/xmpp-im/xmpp_mammanager.h" diff --git a/include/iris/xmpp_mamtask.h b/include/iris/xmpp_mamtask.h new file mode 100644 index 00000000..f9ccc0c8 --- /dev/null +++ b/include/iris/xmpp_mamtask.h @@ -0,0 +1 @@ +#include "xmpp/xmpp-im/xmpp_mamtask.h" diff --git a/src/xmpp/CMakeLists.txt b/src/xmpp/CMakeLists.txt index 2d754e6f..69b4b74f 100644 --- a/src/xmpp/CMakeLists.txt +++ b/src/xmpp/CMakeLists.txt @@ -31,10 +31,12 @@ set(XMPP_IM_HEADERS xmpp-im/xmpp_thumbs.h xmpp-im/xmpp_agentitem.h xmpp-im/xmpp_captcha.h + xmpp-im/xmpp_carbons.h xmpp-im/xmpp_chatstate.h xmpp-im/xmpp_discoitem.h xmpp-im/xmpp_features.h xmpp-im/xmpp_form.h + xmpp-im/xmpp_forwarding.h xmpp-im/xmpp_htmlelement.h xmpp-im/xmpp_httpauthrequest.h xmpp-im/xmpp_liveroster.h @@ -69,6 +71,8 @@ set(XMPP_IM_HEADERS xmpp-im/xmpp_bytestream.h xmpp-im/xmpp_client.h xmpp-im/xmpp_discoinfotask.h + xmpp-im/xmpp_mamtask.h + xmpp-im/xmpp_mammanager.h xmpp-im/xmpp_ibb.h xmpp-im/xmpp_serverinfomanager.h xmpp-im/xmpp_task.h @@ -121,10 +125,14 @@ target_sources(iris PRIVATE xmpp-im/xmpp_bitsofbinary.cpp xmpp-im/xmpp_bytestream.cpp xmpp-im/xmpp_caps.cpp + xmpp-im/xmpp_carbons.cpp xmpp-im/xmpp_discoinfotask.cpp xmpp-im/xmpp_discoitem.cpp xmpp-im/xmpp_hash.cpp xmpp-im/xmpp_ibb.cpp + xmpp-im/xmpp_forwarding.cpp + xmpp-im/xmpp_mamtask.cpp + xmpp-im/xmpp_mammanager.cpp xmpp-im/xmpp_reference.cpp xmpp-im/xmpp_serverinfomanager.cpp xmpp-im/xmpp_subsets.cpp diff --git a/src/xmpp/xmpp-im/client.cpp b/src/xmpp/xmpp-im/client.cpp index f9851b45..36b4a84e 100644 --- a/src/xmpp/xmpp-im/client.cpp +++ b/src/xmpp/xmpp-im/client.cpp @@ -79,6 +79,7 @@ #include "xmpp/xmpp-core/protocol.h" #include "xmpp_bitsofbinary.h" #include "xmpp_caps.h" +#include "xmpp_carbons.h" #include "xmpp_externalservicediscovery.h" #include "xmpp_hash.h" #include "xmpp_ibb.h" @@ -134,6 +135,7 @@ class Client::ClientPrivate { LiveRoster roster; ResourceList resourceList; CapsManager *capsman = nullptr; + CarbonsManager *carbonsman = nullptr; TcpPortReserver *tcpPortReserver = nullptr; S5BManager *s5bman = nullptr; Jingle::S5B::Manager *jingleS5BManager = nullptr; @@ -149,6 +151,7 @@ class Client::ClientPrivate { Jingle::Manager *jingleManager = nullptr; QList groupChatList; EncryptionHandler *encryptionHandler = nullptr; + JT_PushMessage *pushMessage = nullptr; }; Client::Client(QObject *par) : QObject(par) @@ -237,8 +240,9 @@ void Client::start(const QString &host, const QString &user, const QString &pass connect(pp, SIGNAL(subscription(Jid, QString, QString)), SLOT(ppSubscription(Jid, QString, QString))); connect(pp, SIGNAL(presence(Jid, Status)), SLOT(ppPresence(Jid, Status))); - JT_PushMessage *pm = new JT_PushMessage(rootTask(), d->encryptionHandler); - connect(pm, SIGNAL(message(Message)), SLOT(pmMessage(Message))); + d->pushMessage = new JT_PushMessage(rootTask(), d->encryptionHandler); + connect(d->pushMessage, SIGNAL(message(Message)), SLOT(pmMessage(Message))); + d->carbonsman = new CarbonsManager(d->pushMessage); JT_PushRoster *pr = new JT_PushRoster(rootTask()); connect(pr, SIGNAL(roster(Roster)), SLOT(prRoster(Roster))); @@ -305,6 +309,10 @@ Jingle::Manager *Client::jingleManager() const { return d->jingleManager; } bool Client::isActive() const { return d->active; } +CarbonsManager *Client::carbonsManager() const { return d->carbonsman; } + +JT_PushMessage *Client::pushMessage() const { return d->pushMessage; } + QString Client::groupChatPassword(const QString &host, const QString &room) const { Jid jid(room + "@" + host); diff --git a/src/xmpp/xmpp-im/types.cpp b/src/xmpp/xmpp-im/types.cpp index db99cd2d..14e92c50 100644 --- a/src/xmpp/xmpp-im/types.cpp +++ b/src/xmpp/xmpp-im/types.cpp @@ -21,7 +21,9 @@ #include "xmpp/xmpp-core/protocol.h" #include "xmpp_bitsofbinary.h" #include "xmpp_captcha.h" +#include "xmpp_carbons.h" #include "xmpp_features.h" +#include "xmpp_forwarding.h" #include "xmpp_ibb.h" #include "xmpp_reference.h" #include "xmpp_xmlcommon.h" @@ -732,7 +734,6 @@ class Message::Private : public QSharedData { QMap htmlElements; QDomElement sxe; QList bobDataList; - Jid forwardedFrom; QList mucStatuses; QList mucInvites; @@ -743,14 +744,14 @@ class Message::Private : public QSharedData { bool spooled = false, wasEncrypted = false; // XEP-0280 Message Carbons - bool isDisabledCarbons = false; - Message::CarbonDir carbonDir = Message::NoCarbon; // it's a forwarded message + bool carbonsPrivate = false; Message::ProcessingHints processingHints; QString replaceId; QString originId; // XEP-0359 QString encryptionProtocol; // XEP-0380 Message::StanzaId stanzaId; // XEP-0359 QList references; // XEP-0385 and XEP-0372 + Forwarding forwarding; // XEP-0297 Message::Reactions reactions; // XEP-0444 QString retraction; // XEP-0424 }; @@ -1134,17 +1135,44 @@ QList Message::bobDataList() const { return d ? d->bobDataList : QList< IBBData Message::ibbData() const { return d ? d->ibbData : IBBData(); } -void Message::setDisabledCarbons(bool disabled) { MessageD()->isDisabledCarbons = disabled; } +//! \brief Returns Jid of the remote contact +//! +//! Returns Jid of the remote contact for the original message +//! which may be wrapped using carbons. It is useful when a client +//! needs to know in which window it should display the message. +//! So it is not always just from(). +Jid Message::displayJid() const +{ + if (!d) + return Jid(); + + switch (d->forwarding.type()) { + case Forwarding::ForwardedCarbonsSent: + return d->forwarding.message().to(); + case Forwarding::ForwardedCarbonsReceived: + return d->forwarding.message().from(); + default: + break; + } + return from(); +} + +//! \brief Returns either the message inside the carbons or itself. +Message Message::displayMessage() const +{ + if (d && d->forwarding.isCarbons()) + return d->forwarding.message(); -bool Message::isDisabledCarbons() const { return d && d->isDisabledCarbons; } + return *this; +} -void Message::setCarbonDirection(Message::CarbonDir cd) { MessageD()->carbonDir = cd; } +void Message::setCarbonsPrivate(bool enable) { MessageD()->carbonsPrivate = enable; } -Message::CarbonDir Message::carbonDirection() const { return d ? d->carbonDir : NoCarbon; } +bool Message::carbonsPrivate() const { return (d && d->carbonsPrivate); } -void Message::setForwardedFrom(const Jid &jid) { MessageD()->forwardedFrom = jid; } +void Message::setForwarded(const Forwarding &frw) { MessageD()->forwarding = frw; } -Jid Message::forwardedFrom() const { return d ? d->forwardedFrom : Jid(); } +const Forwarding &Message::forwarded() const { return d->forwarding; } bool Message::spooled() const { return d && d->spooled; } @@ -1400,10 +1428,10 @@ Stanza Message::toStanza(Stream *stream) const } // Avoiding Carbons - if (isDisabledCarbons()) { - QDomElement e = s.createElement("urn:xmpp:carbons:2", "private"); - s.appendChild(e); + if (d->carbonsPrivate) { + s.appendChild(CarbonsManager::privateElement(stream->doc())); } + if (!d->replaceId.isEmpty()) { QDomElement e = s.createElement("urn:xmpp:message-correct:0", "replace"); e.setAttribute("id", d->replaceId); @@ -1440,6 +1468,10 @@ Stanza Message::toStanza(Stream *stream) const s.appendChild(e); } + // XEP-0297: Stanza Forwarding + if (d->forwarding.type() != Forwarding::ForwardedNone) + s.appendChild(d->forwarding.toXml(stream)); + // XEP-0372 and XEP-0385 for (auto const &r : std::as_const(d->references)) { s.appendChild(r.toXml(&s.doc())); diff --git a/src/xmpp/xmpp-im/xmpp_carbons.cpp b/src/xmpp/xmpp-im/xmpp_carbons.cpp new file mode 100644 index 00000000..ba4cd211 --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_carbons.cpp @@ -0,0 +1,220 @@ +/* + * xmpp_carbons.cpp - Message Carbons (XEP-0280) + * Copyright (C) 2019 Aleksey Andreev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "xmpp_carbons.h" +#include "xmpp_client.h" +#include "xmpp_forwarding.h" +#include "xmpp_message.h" +#include "xmpp_task.h" +#include "xmpp_tasks.h" +#include "xmpp_xmlcommon.h" + +namespace XMPP { + +static const QString xmlns_carbons(QStringLiteral("urn:xmpp:carbons:2")); + +class CarbonsSubscriber : public JT_PushMessage::Subscriber { +public: + bool xmlEvent(const QDomElement &root, QDomElement &e, Client *client, int userData, bool nested) override; + bool messageEvent(Message &msg, int userData, bool nested) override; + +private: + Forwarding frw; +}; + +class JT_MessageCarbons : public Task { + Q_OBJECT + +public: + JT_MessageCarbons(Task *parent); + + void enable(); + void disable(); + + void onGo() override; + bool take(const QDomElement &e) override; + +private: + QDomElement iq; +}; + +//---------------------------------------------------------------------------- +// JT_MessageCarbons +//---------------------------------------------------------------------------- +JT_MessageCarbons::JT_MessageCarbons(Task *parent) : Task(parent) { } + +void JT_MessageCarbons::enable() +{ + iq = createIQ(doc(), QString::fromLatin1("set"), QString(), id()); + QDomElement enable = doc()->createElement(QString::fromLatin1("enable")); + enable.setAttribute(QString::fromLatin1("xmlns"), xmlns_carbons); + iq.appendChild(enable); +} + +void JT_MessageCarbons::disable() +{ + iq = createIQ(doc(), QString::fromLatin1("set"), QString(), id()); + QDomElement disable = doc()->createElement(QString::fromLatin1("disable")); + disable.setAttribute(QString::fromLatin1("xmlns"), xmlns_carbons); + iq.appendChild(disable); +} + +void JT_MessageCarbons::onGo() +{ + if (!iq.isNull()) + send(iq); +} + +bool JT_MessageCarbons::take(const QDomElement &e) +{ + if (iqVerify(e, Jid(), id())) { + if (e.attribute(QString::fromLatin1("type")) != QString::fromLatin1("result")) + setError(e); + else + setSuccess(); + return true; + } + return false; +} + +//-------------------------------------------------- +// class CarbonsSubscriber +//-------------------------------------------------- + +bool CarbonsSubscriber::xmlEvent(const QDomElement &root, QDomElement &e, Client *client, int userData, bool nested) +{ + bool drop = false; + frw.setType(Forwarding::ForwardedNone); + if (!nested) { + Jid from(root.attribute(QStringLiteral("from"))); + Jid to(root.attribute(QStringLiteral("to"))); + if (from.resource().isEmpty() && from.compare(to, false)) { + QDomElement child = e.firstChildElement(); + while (!child.isNull()) { + if (frw.fromXml(child, client)) { + frw.setType(static_cast(userData)); + break; + } + child = child.nextSiblingElement(); + } + } else + drop = true; + e = QDomElement(); + } + return drop; +} + +bool CarbonsSubscriber::messageEvent(Message &msg, int userData, bool nested) +{ + Q_UNUSED(userData) + if (!nested && frw.type() != Forwarding::ForwardedNone) { + msg.setForwarded(frw); + frw.setType(Forwarding::ForwardedNone); + } + return false; +} + +//-------------------------------------------------- +// class CarbonsManager +//-------------------------------------------------- + +class CarbonsManager::Private { +public: + ~Private() + { + // if (sbs.get()) + // unsubscribe(); + } + + void subscribe() + { + push_m->subscribeXml(sbs.get(), QString::fromLatin1("received"), xmlns_carbons, + Forwarding::ForwardedCarbonsReceived); + push_m->subscribeXml(sbs.get(), QString::fromLatin1("sent"), xmlns_carbons, Forwarding::ForwardedCarbonsSent); + push_m->subscribeMessage(sbs.get(), 0); + } + + void unsubscribe() + { + push_m->unsubscribeXml(sbs.get(), QString::fromLatin1("received"), xmlns_carbons); + push_m->unsubscribeXml(sbs.get(), QString::fromLatin1("sent"), xmlns_carbons); + push_m->unsubscribeMessage(sbs.get()); + } + + JT_PushMessage *push_m; + std::unique_ptr sbs; + bool enable = false; +}; + +CarbonsManager::CarbonsManager(JT_PushMessage *push_m) : QObject(push_m), d(new Private) +{ + d->push_m = push_m; + d->sbs.reset(new CarbonsSubscriber()); +} + +CarbonsManager::~CarbonsManager() { } + +QDomElement CarbonsManager::privateElement(QDomDocument &doc) +{ + return doc.createElementNS(xmlns_carbons, QString::fromLatin1("private")); +} + +void CarbonsManager::setEnabled(bool enable) +{ + if (d->enable == enable) + return; + + if (enable) { + d->subscribe(); + JT_MessageCarbons *jt = new JT_MessageCarbons(d->push_m->client()->rootTask()); + connect( + jt, &JT_MessageCarbons::finished, this, + [this, jt]() { + if (jt->success()) + d->enable = true; + else + d->unsubscribe(); + emit finished(); + }, + Qt::QueuedConnection); + jt->enable(); + jt->go(true); + } else { + JT_MessageCarbons *jt = new JT_MessageCarbons(d->push_m->client()->rootTask()); + connect( + jt, &JT_MessageCarbons::finished, this, + [this]() { + d->enable = false; + d->unsubscribe(); + emit finished(); + }, + Qt::QueuedConnection); + jt->disable(); + jt->go(true); + } +} + +bool CarbonsManager::isEnabled() const { return d->enable; } + +} // namespace XMPP + +#include "xmpp_carbons.moc" diff --git a/src/xmpp/xmpp-im/xmpp_carbons.h b/src/xmpp/xmpp-im/xmpp_carbons.h new file mode 100644 index 00000000..20cbc671 --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_carbons.h @@ -0,0 +1,60 @@ +/* + * xmpp_carbons.h - Message Carbons (XEP-0280) + * Copyright (C) 2019 Aleksey Andreev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef XMPP_CARBONS_H +#define XMPP_CARBONS_H + +#include +#include + +class QDomDocument; +class QDomElement; + +namespace XMPP +{ + class Task; + class Client; + class JT_PushMessage; + + class CarbonsManager : public QObject + { + Q_OBJECT + + public: + CarbonsManager(JT_PushMessage *push_m); + CarbonsManager(const CarbonsManager &) = delete; + CarbonsManager & operator=(const CarbonsManager &) = delete; + ~CarbonsManager(); + + static QDomElement privateElement(QDomDocument &doc); + + void setEnabled(bool enable); + bool isEnabled() const; + + signals: + void finished(); + + private: + class Private; + std::unique_ptr d; + }; +} + +#endif diff --git a/src/xmpp/xmpp-im/xmpp_client.h b/src/xmpp/xmpp-im/xmpp_client.h index 58e5f918..1cd7e39a 100644 --- a/src/xmpp/xmpp-im/xmpp_client.h +++ b/src/xmpp/xmpp-im/xmpp_client.h @@ -31,17 +31,18 @@ class ByteStream; class QDomDocument; class QDomElement; class QNetworkAccessManager; -class QString; namespace XMPP { class BSConnection; class CapsManager; +class CarbonsManager; class ClientStream; class EncryptionHandler; class Features; class FileTransferManager; class HttpFileUploadManager; class IBBManager; +class JT_PushMessage; class JidLinkManager; class LiveRoster; class LiveRosterItem; @@ -70,9 +71,7 @@ namespace Jingle { class Manager; } } -} -namespace XMPP { class Client : public QObject { Q_OBJECT @@ -150,6 +149,8 @@ class Client : public QObject { BoBManager *bobManager() const; JidLinkManager *jidLinkManager() const; CapsManager *capsManager() const; + CarbonsManager *carbonsManager() const; + JT_PushMessage *pushMessage() const; ServerInfoManager *serverInfoManager() const; ExternalServiceDiscovery *externalServiceDiscovery() const; StunDiscoManager *stunDiscoManager() const; diff --git a/src/xmpp/xmpp-im/xmpp_discoinfotask.h b/src/xmpp/xmpp-im/xmpp_discoinfotask.h index 81023223..c651fe8c 100644 --- a/src/xmpp/xmpp-im/xmpp_discoinfotask.h +++ b/src/xmpp/xmpp-im/xmpp_discoinfotask.h @@ -23,7 +23,6 @@ #include "xmpp_task.h" class QDomElement; -class QString; namespace XMPP { class Jid; diff --git a/src/xmpp/xmpp-im/xmpp_forwarding.cpp b/src/xmpp/xmpp-im/xmpp_forwarding.cpp new file mode 100644 index 00000000..09a93504 --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_forwarding.cpp @@ -0,0 +1,204 @@ +/* + * xmpp_forwarding.cpp - Stanza Forwarding (XEP-0297) + * Copyright (C) 2019 Aleksey Andreev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "xmpp_forwarding.h" +#include "xmpp_client.h" +#include "xmpp_message.h" +#include "xmpp_stream.h" +#include "xmpp_tasks.h" +#include "xmpp_xmlcommon.h" + +namespace XMPP { + +static const QString xmlns_forward(QStringLiteral("urn:xmpp:forward:0")); +static const QString xmlns_delay(QStringLiteral("urn:xmpp:delay")); + +//-------------------------------------------------- +// class Forwarding +//-------------------------------------------------- + +Forwarding::Forwarding() : type_(ForwardedNone) { } + +Forwarding::Forwarding(const Forwarding &other) : type_(other.type_), ts_(other.ts_), msg_(other.msg_) { } + +Forwarding::~Forwarding() { } + +Forwarding &Forwarding::operator=(const Forwarding &from) +{ + if (this != &from) { + type_ = from.type_; + ts_ = from.ts_; + msg_ = from.msg_; + } + return *this; +} + +Forwarding::Type Forwarding::type() const { return type_; } + +void Forwarding::setType(Type type) +{ + if (type_ != type) { + type_ = type; + if (type == ForwardedNone) { + ts_ = QDateTime(); + msg_ = Message(); + } + } +} + +bool Forwarding::isCarbons() const { return (type_ == ForwardedCarbonsSent || type_ == ForwardedCarbonsReceived); } + +QDateTime Forwarding::timeStamp() const +{ + if (!ts_.isNull()) + return ts_; + return msg_.timeStamp(); +} + +void Forwarding::setTimeStamp(const QDateTime &ts) { ts_ = ts; } + +Message Forwarding::message() const { return msg_; } + +void Forwarding::setMessage(const Message &msg) { msg_ = msg; } + +bool Forwarding::fromXml(const QDomElement &e, Client *client) +{ + if (e.tagName() != QString::fromLatin1("forwarded") || e.attribute(QString::fromLatin1("xmlns")) != xmlns_forward) + return false; + + bool correct = false; + type_ = Forwarding::ForwardedNone; + QDomElement child = e.firstChildElement(); + while (!child.isNull()) { + if (child.tagName() == QString::fromLatin1("message")) { + if (client->pushMessage()->processXmlSubscribers(child, client, true)) + break; + Stanza s = client->stream().createStanza(addCorrectNS(child)); + Message msg; + if (msg.fromStanza(s, client->manualTimeZoneOffset(), client->timeZoneOffset())) { + if (client->pushMessage()->processMessageSubscribers(msg, true)) + break; + msg_ = msg; + type_ = ForwardedMessage; + correct = true; + } + } else if (child.tagName() == QString::fromLatin1("delay") + && child.attribute(QString::fromLatin1("xmlns")) == xmlns_delay) { + ts_ = QDateTime::fromString(child.attribute(QString::fromLatin1("stamp")).left(19), Qt::ISODate); + } + child = child.nextSiblingElement(); + } + return correct; +} + +QDomElement Forwarding::toXml(Stream *stream) const +{ + if (type_ == ForwardedNone || msg_.isNull()) + return QDomElement(); + + QDomElement e = stream->doc().createElement(QString::fromLatin1("forwarded")); + e.setAttribute(QString::fromLatin1("xmlns"), xmlns_forward); + if (ts_.isValid()) { + QDomElement delay = stream->doc().createElement(QString::fromLatin1("delay")); + delay.setAttribute(QString::fromLatin1("xmlns"), xmlns_delay); + delay.setAttribute(QString::fromLatin1("stamp"), ts_.toUTC().toString(Qt::ISODate) + "Z"); + e.appendChild(delay); + } + e.appendChild(msg_.toStanza(stream).element()); + return e; +} + +//-------------------------------------------------- +// class ForwardingManager +//-------------------------------------------------- + +class ForwardingSubscriber : public JT_PushMessage::Subscriber { +public: + bool xmlEvent(const QDomElement &root, QDomElement &e, Client *c, int userData, bool nested) override + { + Q_UNUSED(root) + Q_UNUSED(userData) + frw.setType(Forwarding::ForwardedNone); + if (!nested) { + Stanza stanza = c->stream().createStanza(e); + if (!stanza.isNull() && stanza.kind() == Stanza::Message) { + frw.fromXml(e, c); + } + } + return false; + } + + bool messageEvent(Message &msg, int userData, bool nested) override + { + Q_UNUSED(userData) + if (!nested && frw.type() != Forwarding::ForwardedNone) { + msg.setForwarded(frw); + frw.setType(Forwarding::ForwardedNone); + } + return false; + } + +private: + Forwarding frw; +}; + +//-------------------------------------------------- +// class ForwardingManager +//-------------------------------------------------- + +class ForwardingManager::Private { +public: + ~Private() + { + // if (sbs.get()) { + // push_m->unsubscribeXml(sbs.get(), QLatin1String("forwarded"), xmlns_forward); + // push_m->unsubscribeMessage(sbs.get()); + // } + } + + JT_PushMessage *push_m; + std::unique_ptr sbs; + bool enabled = false; +}; + +ForwardingManager::ForwardingManager(JT_PushMessage *push_m) : QObject(push_m), d(new Private) { d->push_m = push_m; } + +ForwardingManager::~ForwardingManager() { } + +void ForwardingManager::setEnabled(bool enabled) +{ + if (d->enabled == enabled) + return; + + if (enabled) { + d->sbs.reset(new ForwardingSubscriber()); + d->push_m->subscribeXml(d->sbs.get(), QString::fromLatin1("forwarded"), xmlns_forward, 0); + d->push_m->subscribeMessage(d->sbs.get(), 0); + } else { + d->push_m->unsubscribeXml(d->sbs.get(), QString::fromLatin1("forwarded"), xmlns_forward); + d->push_m->unsubscribeMessage(d->sbs.get()); + d->sbs.reset(); + } + d->enabled = enabled; +} + +bool ForwardingManager::isEnabled() const { return d->enabled; } + +} // namespace XMPP diff --git a/src/xmpp/xmpp-im/xmpp_forwarding.h b/src/xmpp/xmpp-im/xmpp_forwarding.h new file mode 100644 index 00000000..c9f37bce --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_forwarding.h @@ -0,0 +1,92 @@ +/* + * xmpp_forwarding.h - Stanza Forwarding (XEP-0297) + * Copyright (C) 2019 Aleksey Andreev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef XMPP_FORWARDING_H +#define XMPP_FORWARDING_H + +#include +#include +#include +#include + +#include "xmpp_message.h" + +namespace XMPP +{ + class Client; + class Stream; + class Message; + class JT_PushMessage; + + class Forwarding + { + public: + Forwarding(); + Forwarding(const Forwarding &); + ~Forwarding(); + + Forwarding & operator=(const Forwarding &); + + enum Type { + ForwardedNone, + ForwardedMessage, // XEP-0297 + ForwardedCarbonsReceived, // XEP-0280 + ForwardedCarbonsSent, // XEP-0280 + }; + Type type() const; + void setType(Type type); + bool isCarbons() const; + + QDateTime timeStamp() const; + void setTimeStamp(const QDateTime &ts); + + Message message() const; + void setMessage(const Message &msg); + + bool fromXml(const QDomElement &e, Client *client); + QDomElement toXml(Stream *stream) const; + + private: + Type type_; + QDateTime ts_; + Message msg_; + }; + + class ForwardingManager : public QObject + { + Q_OBJECT + + public: + ForwardingManager(JT_PushMessage *push_m); + ForwardingManager(const ForwardingManager &) = delete; + ForwardingManager & operator=(const ForwardingManager &) = delete; + ~ForwardingManager(); + + void setEnabled(bool enabled); + bool isEnabled() const; + + private: + class Private; + std::unique_ptr d; + }; + +} + +#endif diff --git a/src/xmpp/xmpp-im/xmpp_mammanager.cpp b/src/xmpp/xmpp-im/xmpp_mammanager.cpp new file mode 100644 index 00000000..a0e686ee --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_mammanager.cpp @@ -0,0 +1,88 @@ +/* + * xmpp_mammanager.cpp - XEP-0313 Message Archive Management + * Copyright (C) 2024 mcneb10 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ + +#include "xmpp_mammanager.h" + +using namespace XMPP; + +class MAMManager::Private { +public: + int mamPageSize; + int mamMaxMessages; + bool flipPages; + bool backwards; + Client *client; +}; + +MAMManager::MAMManager(Client *client, int mamPageSize, int mamMaxMessages, bool flipPages, bool backwards) +{ + d = new Private; + + d->client = client; + d->mamPageSize = mamPageSize; + d->mamMaxMessages = mamMaxMessages; + d->flipPages = flipPages; + d->backwards = backwards; +} + +MAMManager::~MAMManager() { delete d; } + +// TODO: review the safety of these methods/object lifetimes +MAMTask *MAMManager::getFullArchive(const Jid &j, const bool allowMUCArchives) +{ + auto task = new MAMTask(d->client->rootTask()); + + task->get(j, QString(), QString(), allowMUCArchives, d->mamPageSize, d->mamMaxMessages, d->flipPages, d->backwards); + return task; +} + +MAMTask *MAMManager::getArchiveByIDRange(const Jid &j, const QString &fromID, const QString &toID, + const bool allowMUCArchives) +{ + auto task = new MAMTask(d->client->rootTask()); + + task->get(j, fromID, toID, allowMUCArchives, d->mamPageSize, d->mamMaxMessages, d->flipPages, d->backwards); + return task; +} + +MAMTask *MAMManager::getArchiveByTimeRange(const Jid &j, const QDateTime &from, const QDateTime &to, + const bool allowMUCArchives) +{ + auto task = new MAMTask(d->client->rootTask()); + + task->get(j, from, to, allowMUCArchives, d->mamPageSize, d->mamMaxMessages, d->flipPages, d->backwards); + return task; +} + +MAMTask *MAMManager::getLatestMessagesFromArchive(const Jid &j, const QString &fromID, const bool allowMUCArchives, + int amount) +{ + auto task = new MAMTask(d->client->rootTask()); + + task->get(j, fromID, QString(), allowMUCArchives, d->mamPageSize, amount, true, true); + return task; +} + +MAMTask *MAMManager::getMessagesBeforeID(const Jid &j, const QString &toID, const bool allowMUCArchives, int amount) +{ + auto task = new MAMTask(d->client->rootTask()); + + task->get(j, QString(), toID, allowMUCArchives, d->mamPageSize, amount, true, true); + return task; +} diff --git a/src/xmpp/xmpp-im/xmpp_mammanager.h b/src/xmpp/xmpp-im/xmpp_mammanager.h new file mode 100644 index 00000000..9c87e660 --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_mammanager.h @@ -0,0 +1,53 @@ +/* + * xmpp_mammanager.h - XEP-0313 Message Archive Management + * Copyright (C) 2024 mcneb10 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ + +#ifndef XMPP_MAM_MANAGER_H +#define XMPP_MAM_MANAGER_H + +#include "xmpp_client.h" +#include "xmpp_mamtask.h" + +#include +#include + +namespace XMPP { +class MAMManager : public QObject { + Q_OBJECT +public: + MAMManager(Client *client, int mamPageSize = 10, int mamMaxMessages = 0, bool flipPages = true, + bool backwards = true); + ~MAMManager(); + + MAMTask *getFullArchive(const Jid &j, const bool allowMUCArchives = true); + MAMTask *getArchiveByIDRange(const Jid &j, const QString &fromID, const QString &toID, + const bool allowMUCArchives = true); + MAMTask *getArchiveByTimeRange(const Jid &j, const QDateTime &from, const QDateTime &to, + const bool allowMUCArchives = true); + MAMTask *getLatestMessagesFromArchive(const Jid &j, const QString &fromID, const bool allowMUCArchives = true, + int amount = 100); + MAMTask *getMessagesBeforeID(const Jid &j, const QString &toID, const bool allowMUCArchives = true, + int amount = 100); + +private: + class Private; + Private *d; +}; +} + +#endif diff --git a/src/xmpp/xmpp-im/xmpp_mamtask.cpp b/src/xmpp/xmpp-im/xmpp_mamtask.cpp new file mode 100644 index 00000000..87eee3d3 --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_mamtask.cpp @@ -0,0 +1,278 @@ +/* + * xmpp_mamtask.cpp - XEP-0313 Message Archive Management + * Copyright (C) 2024 mcneb10 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ + +#include "xmpp_mamtask.h" + +using namespace XMLHelper; +using namespace XMPP; + +class MAMTask::Private { +public: + int mamPageSize; // TODO: this is the max page size for MAM request. Should be made into a config option in Psi+ + int mamMaxMessages; // maximum mam pages total, also should be config. zero means unlimited + int messagesFetched; + bool flipPages; + bool backwards; + bool allowMUCArchives; + bool metadataFetched; + Jid j; + MAMTask *q; + QString firstID; + QString lastID; + QString lastArchiveID; + QString fromID; + QString toID; + QString mainQueryID; + QString currentPageQueryID; + QString currentPageQueryIQID; + QDateTime from; + QDateTime to; + QList archive; + + void getPage(); + void getArchiveMetadata(); + XData makeMAMFilter(); +}; + +MAMTask::MAMTask(Task *parent) : Task(parent) { d = new Private; } +MAMTask::MAMTask(const MAMTask &x) : Task(x.parent()) { d = x.d; } +MAMTask::~MAMTask() { delete d; } + +const QList &MAMTask::archive() const { return d->archive; } + +XData MAMTask::Private::makeMAMFilter() +{ + XData::FieldList fl; + + XData::Field with; + with.setType(XData::Field::Field_JidSingle); + with.setVar(QLatin1String("with")); + with.setValue(QStringList(j.full())); + fl.append(with); + + XData::Field includeGroupchat; + includeGroupchat.setType(XData::Field::Field_Boolean); + includeGroupchat.setVar(QLatin1String("include-groupchat")); + includeGroupchat.setValue(QStringList(QLatin1String(allowMUCArchives ? "true" : "false"))); + fl.append(includeGroupchat); + + if (from.isValid()) { + XData::Field start; + start.setType(XData::Field::Field_TextSingle); + start.setVar(QLatin1String("start")); + from.setTimeSpec(Qt::UTC); + start.setValue(QStringList(from.toString())); + fl.append(start); + } + + if (to.isValid()) { + XData::Field end; + end.setType(XData::Field::Field_TextSingle); + end.setVar(QLatin1String("end")); + to.setTimeSpec(Qt::UTC); + end.setValue(QStringList(to.toString())); + fl.append(end); + } + + if (!fromID.isNull()) { + XData::Field start_id; + start_id.setType(XData::Field::Field_TextSingle); + start_id.setVar(QLatin1String("after-id")); + start_id.setValue(QStringList(fromID)); + fl.append(start_id); + } + + if (!toID.isNull()) { + XData::Field end_id; + end_id.setType(XData::Field::Field_TextSingle); + end_id.setVar(QLatin1String("before-id")); + end_id.setValue(QStringList(toID)); + fl.append(end_id); + } + + XData x; + x.setType(XData::Data_Submit); + x.setFields(fl); + x.setRegistrarType(XMPP_MAM_NAMESPACE); + + return x; +} + +void MAMTask::Private::getPage() +{ + currentPageQueryIQID = q->genUniqueID(); + QDomElement iq = createIQ(q->doc(), QLatin1String("set"), QLatin1String(), currentPageQueryIQID); + QDomElement query = q->doc()->createElementNS(XMPP_MAM_NAMESPACE, QLatin1String("query")); + currentPageQueryID = q->genUniqueID(); + query.setAttribute(QLatin1String("queryid"), currentPageQueryID); + XData x = makeMAMFilter(); + + SubsetsClientManager rsm; + rsm.setMax(mamMaxMessages); + + if (flipPages) + query.appendChild(emptyTag(q->doc(), QLatin1String("flip-page"))); + + if (lastArchiveID.isNull()) { + if (backwards) { + rsm.getLast(); + } else { + rsm.getFirst(); + } + } else { + if (backwards) { + rsm.setFirstID(lastArchiveID); + rsm.getPrevious(); + } else { + rsm.setLastID(lastArchiveID); + rsm.getNext(); + } + } + + query.appendChild(x.toXml(q->doc())); + query.appendChild(rsm.makeQueryElement(q->doc())); + iq.appendChild(query); + q->send(iq); +} + +void MAMTask::Private::getArchiveMetadata() +{ + // Craft a query to get the first and last messages in an archive + mainQueryID = q->genUniqueID(); + QDomElement iq = createIQ(q->doc(), QLatin1String("get"), QLatin1String(), mainQueryID); + QDomElement metadata = emptyTag(q->doc(), QLatin1String("metadata")); + metadata.setAttribute(QLatin1String("xmlns"), XMPP_MAM_NAMESPACE); + iq.appendChild(metadata); + + q->send(iq); +} + +// Note: Set `j` to a resource if you just want to query that resource +// if you want to query all resources, set `j` to the bare JID + +// Filter by time range +void MAMTask::get(const Jid &j, const QDateTime &from, const QDateTime &to, const bool allowMUCArchives, + int mamPageSize, int mamMaxMessages, bool flipPages, bool backwards) +{ + d->archive = {}; + d->messagesFetched = 0; + d->metadataFetched = false; + + d->j = j; + d->from = from; + d->to = to; + d->allowMUCArchives = allowMUCArchives; + d->mamPageSize = mamPageSize; + d->mamMaxMessages = mamMaxMessages; + d->flipPages = flipPages; + d->backwards = backwards; + d->q = this; +} + +// Filter by id range +void MAMTask::get(const Jid &j, const QString &fromID, const QString &toID, const bool allowMUCArchives, + int mamPageSize, int mamMaxMessages, bool flipPages, bool backwards) +{ + d->archive = {}; + d->messagesFetched = 0; + d->metadataFetched = false; + + d->j = j; + d->fromID = fromID; + d->toID = toID; + d->allowMUCArchives = allowMUCArchives; + d->mamPageSize = mamPageSize; + d->mamMaxMessages = mamMaxMessages; + d->flipPages = flipPages; + d->backwards = backwards; +} + +void MAMTask::onGo() { d->getArchiveMetadata(); } + +bool MAMTask::take(const QDomElement &x) +{ + if (d->metadataFetched) { + if (iqVerify(x, QString(), d->currentPageQueryIQID)) { + if (!x.elementsByTagNameNS(QLatin1String("urn:ietf:params:xml:ns:xmpp-stanzas"), + QLatin1String("item-not-found")) + .isEmpty()) { + setError(2, "First or last stanza UID of filter was not found in the archive"); + return true; + } else if (!x.elementsByTagNameNS(XMPP_MAM_NAMESPACE, QLatin1String("fin")).isEmpty()) { + // We are done? + //setSuccess(); + //return true; + return false; // TODO: testing + } + // Probably ignore it + return false; + } + + QDomElement result = x.firstChildElement("result"); + if (result != QDomElement() && result.namespaceURI() == XMPP_MAM_NAMESPACE + && result.attribute(QLatin1String("queryid")) == d->currentPageQueryID) { + + d->archive.append(result); + d->lastArchiveID = result.attribute(QLatin1String("id")); + d->messagesFetched = d->messagesFetched + 1; + + // Check if we are done + if (result.attribute(QLatin1String("id")) == d->lastID || d->messagesFetched >= d->mamMaxMessages) { + setSuccess(); + } else if (d->messagesFetched % d->mamPageSize == 0) { + d->getPage(); + } + } + } else { + if (!iqVerify(x, QString(), d->mainQueryID)) + return false; + + // Return if the archive is empty + QDomElement queryMetadata = x.firstChildElement(QLatin1String("metadata")); + if(queryMetadata == QDomElement()) { + setError(1, "Malformed server metadata response"); + return true; + } + if (!queryMetadata.hasChildNodes()) { + // No data in archive + setSuccess(); + return true; + } + + QDomElement start_id = queryMetadata.firstChildElement(QLatin1String("start")); + QDomElement end_id = queryMetadata.firstChildElement(QLatin1String("end")); + + if (start_id.isNull() || end_id.isNull()) { + setError(1, "Malformed server metadata response"); + return true; + } + + if (d->backwards) { + d->lastID = start_id.attribute(QLatin1String("id")); + d->firstID = end_id.attribute(QLatin1String("id")); + } else { + d->firstID = start_id.attribute(QLatin1String("id")); + d->lastID = end_id.attribute(QLatin1String("id")); + } + d->metadataFetched = true; + d->getPage(); + } + + return true; +} diff --git a/src/xmpp/xmpp-im/xmpp_mamtask.h b/src/xmpp/xmpp-im/xmpp_mamtask.h new file mode 100644 index 00000000..63e8535a --- /dev/null +++ b/src/xmpp/xmpp-im/xmpp_mamtask.h @@ -0,0 +1,67 @@ +/* + * xmpp_mamtask.h - XEP-0313 Message Archive Management + * Copyright (C) 2024 mcneb10 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ + +#ifndef XMPP_MAM_TASK_H +#define XMPP_MAM_TASK_H + +#include "xmpp/jid/jid.h" +#include "xmpp_subsets.h" +#include "xmpp_task.h" +#include "xmpp_xdata.h" +#include "xmpp_xmlcommon.h" + +#include + +#define XMPP_MAM_NAMESPACE QLatin1String("urn:xmpp:mam:2") + +class QDomElement; +class QString; + +namespace XMPP { +class Jid; + +class MAMTask : public Task { + Q_OBJECT +public: + explicit MAMTask(Task *); + MAMTask(const MAMTask &x); + ~MAMTask(); + + const QList &archive() const; + + // Time filter + void get(const Jid &j, const QDateTime &from = QDateTime(), const QDateTime &to = QDateTime(), + const bool allowMUCArchives = true, int mamPageSize = 10, int mamMaxMessages = 0, bool flipPages = true, + bool backwards = true); + + // ID Filter + void get(const Jid &j, const QString &fromID = QString(), const QString &toID = QString(), + const bool allowMUCArchives = true, int mamPageSize = 10, int mamMaxMessages = 0, bool flipPages = true, + bool backwards = true); + + void onGo(); + bool take(const QDomElement &); + +private: + class Private; + Private *d; +}; +} // namespace XMPP + +#endif diff --git a/src/xmpp/xmpp-im/xmpp_message.h b/src/xmpp/xmpp-im/xmpp_message.h index 45b5965d..2afef8a4 100644 --- a/src/xmpp/xmpp-im/xmpp_message.h +++ b/src/xmpp/xmpp-im/xmpp_message.h @@ -19,7 +19,6 @@ #ifndef XMPP_MESSAGE_H #define XMPP_MESSAGE_H -#include "iris/xmpp_stanza.h" #include "xmpp_address.h" #include "xmpp_chatstate.h" #include "xmpp_muc.h" @@ -36,6 +35,7 @@ class QString; namespace XMPP { class BoBData; +class Forwarding; class HTMLElement; class HttpAuthRequest; class IBBData; @@ -50,12 +50,6 @@ typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, ComposingEvent, Can class Message { public: - enum CarbonDir : quint8 { - NoCarbon, - Received, // other party messages are sent to another own client - Sent // own messages are sent from other clients - }; - enum class Type { Chat, Error, Groupchat, Headline, Normal }; // XEP-0334 @@ -87,7 +81,7 @@ class Message { Type type() const; QString typeStr() const; QString lang() const; - QString subject(const QString &lang = QString()) const; + QString subject(const QString &lang = {}) const; QString subject(const QLocale &lang) const; StringMap subjectMap() const; QString body(const QString &lang = "") const; @@ -100,8 +94,8 @@ class Message { void setId(const QString &s); void setType(Type type); void setLang(const QString &s); - void setSubject(const QString &s, const QString &lang = ""); - void setBody(const QString &s, const QString &lang = ""); + void setSubject(const QString &s, const QString &lang = {}); + void setBody(const QString &s, const QString &lang = {}); void setThread(const QString &s, bool send = false); void setError(const Stanza::Error &err); @@ -115,8 +109,8 @@ class Message { void setTimeStamp(const QDateTime &ts, bool send = false); // XEP-0071 - HTMLElement html(const QString &lang = "") const; - void setHTML(const HTMLElement &s, const QString &lang = ""); + HTMLElement html(const QString &lang = {}) const; + void setHTML(const HTMLElement &s, const QString &lang = {}); bool containsHTML() const; // XEP-0066 @@ -183,14 +177,15 @@ class Message { IBBData ibbData() const; // XEP-0280 Message Carbons - void setDisabledCarbons(bool disabled); - bool isDisabledCarbons() const; - void setCarbonDirection(CarbonDir); - CarbonDir carbonDirection() const; + Jid displayJid() const; + Message displayMessage() const; + void setCarbonsPrivate(bool enable); + bool carbonsPrivate() const; // XEP-0297 - void setForwardedFrom(const Jid &jid); - Jid forwardedFrom() const; + void setForwarded(const Forwarding &frw); + // note, the next method has to be called only on not-null message + const Forwarding &forwarded() const; // XEP-0308 QString replaceId() const; diff --git a/src/xmpp/xmpp-im/xmpp_subsets.cpp b/src/xmpp/xmpp-im/xmpp_subsets.cpp index 5925bbab..f1f75fdc 100644 --- a/src/xmpp/xmpp-im/xmpp_subsets.cpp +++ b/src/xmpp/xmpp-im/xmpp_subsets.cpp @@ -165,6 +165,8 @@ bool SubsetsClientManager::isLast() const { return d->result.last; } int SubsetsClientManager::count() const { return d->result.count; } void SubsetsClientManager::setMax(int max) { d->query.max = max; } +void SubsetsClientManager::setFirstID(const QString& a) { d->result.firstId = a; } +void SubsetsClientManager::setLastID(const QString& a) { d->result.lastId = a; } QDomElement SubsetsClientManager::findElement(const QDomElement &el, bool child) { diff --git a/src/xmpp/xmpp-im/xmpp_subsets.h b/src/xmpp/xmpp-im/xmpp_subsets.h index 4e6bbd24..a940b035 100644 --- a/src/xmpp/xmpp-im/xmpp_subsets.h +++ b/src/xmpp/xmpp-im/xmpp_subsets.h @@ -34,6 +34,8 @@ class SubsetsClientManager { bool isLast() const; int count() const; void setMax(int max); + void setFirstID(const QString&); + void setLastID(const QString&); void getCount(); void getFirst(); diff --git a/src/xmpp/xmpp-im/xmpp_task.cpp b/src/xmpp/xmpp-im/xmpp_task.cpp index 7c4c2a02..f50842a6 100644 --- a/src/xmpp/xmpp-im/xmpp_task.cpp +++ b/src/xmpp/xmpp-im/xmpp_task.cpp @@ -83,6 +83,8 @@ QDomDocument *Task::doc() const { return client()->doc(); } QString Task::id() const { return d->id; } +QString Task::genUniqueID() { return client()->genUniqueId(); } + bool Task::success() const { return d->success; } int Task::statusCode() const { return d->statusCode; } diff --git a/src/xmpp/xmpp-im/xmpp_task.h b/src/xmpp/xmpp-im/xmpp_task.h index 15e2d260..a0b031de 100644 --- a/src/xmpp/xmpp-im/xmpp_task.h +++ b/src/xmpp/xmpp-im/xmpp_task.h @@ -44,6 +44,7 @@ class Task : public QObject { Client *client() const; QDomDocument *doc() const; QString id() const; + QString genUniqueID(); bool success() const; int statusCode() const; diff --git a/src/xmpp/xmpp-im/xmpp_tasks.cpp b/src/xmpp/xmpp-im/xmpp_tasks.cpp index 2ee5f3a5..90d8232c 100644 --- a/src/xmpp/xmpp-im/xmpp_tasks.cpp +++ b/src/xmpp/xmpp-im/xmpp_tasks.cpp @@ -28,12 +28,16 @@ #include "xmpp_vcard.h" #include "xmpp_xmlcommon.h" +#include #include #include #include using namespace XMPP; +#define GET_SUBSCRIBER_ITERATOR(list, sbs) \ + std::find_if(list.begin(), list.end(), [sbs](const Private::SubsData &value) { return value.sbs == sbs; }) + static QString lineEncode(QString str) { static QRegularExpression backslash("\\\\"); @@ -821,16 +825,133 @@ void JT_Message::onGo() //---------------------------------------------------------------------------- class JT_PushMessage::Private { public: - EncryptionHandler *m_encryptionHandler; + struct SubsData { + Subscriber *sbs = nullptr; + int userData = -1; + }; + using SubsDataList = QVector; + EncryptionHandler *m_encryptionHandler; + QHash subsData; + SubsDataList subsMData; + + QString genKey(const QString &s1, const QString &s2) { return QString::fromLatin1("%1&%2").arg(s1, s2); } + + bool processChildStanzaNode(const QDomElement &root, QDomElement &e, Client *c, bool nested) + { + QString tagName = e.tagName(); + QString xmlnsStr = e.attribute(QString::fromLatin1("xmlns")); + QString key = genKey(tagName, xmlnsStr); + auto it = subsData.constFind(key); + if (it != subsData.constEnd()) { + foreach (const SubsData &sd, it.value()) { + if (sd.sbs->xmlEvent(root, e, c, sd.userData, nested)) + return true; + if (e.tagName() != tagName || e.attribute(QString::fromLatin1("xmlns")) != tagName) + return false; + } + } + return false; + } + + bool processMessage(Message &msg, bool nested) + { + foreach (const SubsData &sd, subsMData) { + if (sd.sbs->messageEvent(msg, sd.userData, nested)) + return true; + } + return false; + } }; -JT_PushMessage::JT_PushMessage(Task *parent, EncryptionHandler *encryptionHandler) : Task(parent) +JT_PushMessage::Subscriber::~Subscriber() { } + +bool JT_PushMessage::Subscriber::xmlEvent(const QDomElement &root, QDomElement &e, Client *c, int userData, bool nested) +{ + Q_UNUSED(root) + Q_UNUSED(e) + Q_UNUSED(c) + Q_UNUSED(userData) + Q_UNUSED(nested) + return false; +} + +bool JT_PushMessage::Subscriber::messageEvent(Message &msg, int userData, bool nested) +{ + Q_UNUSED(msg); + Q_UNUSED(userData); + Q_UNUSED(nested) + return false; +} + +JT_PushMessage::JT_PushMessage(Task *parent, EncryptionHandler *encryptionHandler) : Task(parent), d(new Private) { - d = new Private; d->m_encryptionHandler = encryptionHandler; } -JT_PushMessage::~JT_PushMessage() { delete d; } +JT_PushMessage::~JT_PushMessage() { } + +void JT_PushMessage::subscribeXml(Subscriber *sbs, const QString &tagName, const QString &xmlnsStr, int userData) +{ + QString key = d->genKey(tagName, xmlnsStr); + auto it = d->subsData.find(key); + if (it != d->subsData.end()) { + Private::SubsDataList &list = it.value(); + auto lit = GET_SUBSCRIBER_ITERATOR(list, sbs); + if (lit == list.end()) + list.append({ sbs, userData }); + } else { + d->subsData.insert(key, { { sbs, userData } }); + } +} + +void JT_PushMessage::unsubscribeXml(Subscriber *sbs, const QString &tagName, const QString &xmlnsStr) +{ + QString key = d->genKey(tagName, xmlnsStr); + auto it = d->subsData.find(key); + if (it != d->subsData.end()) { + Private::SubsDataList &list = it.value(); + auto lit = GET_SUBSCRIBER_ITERATOR(list, sbs); + if (lit != list.end()) { + list.erase(lit); + if (list.isEmpty()) + d->subsData.erase(it); + } + } +} + +void JT_PushMessage::subscribeMessage(Subscriber *sbs, int userData) +{ + auto &list = d->subsMData; + auto lit = GET_SUBSCRIBER_ITERATOR(list, sbs); + if (lit == list.end()) + list.append({ sbs, userData }); +} + +void JT_PushMessage::unsubscribeMessage(Subscriber *sbs) +{ + auto &list = d->subsMData; + auto lit = GET_SUBSCRIBER_ITERATOR(list, sbs); + if (lit != list.end()) + list.erase(lit); +} + +bool JT_PushMessage::processXmlSubscribers(QDomElement &el, Client *client, bool nested) +{ + bool processed = false; + QDomElement ch = el.firstChildElement(); + while (!ch.isNull()) { + QDomElement next = ch.nextSiblingElement(); + bool res = d->processChildStanzaNode(el, ch, client, nested); + if (res) + processed = true; + if (res || ch.isNull()) + el.removeChild(ch); + ch = next; + } + return (processed && el.childNodes().length() == 0); +} + +bool JT_PushMessage::processMessageSubscribers(Message &msg, bool nested) { return d->processMessage(msg, nested); } bool JT_PushMessage::take(const QDomElement &e) { @@ -848,39 +969,10 @@ bool JT_PushMessage::take(const QDomElement &e) } } - QDomElement forward; - Message::CarbonDir cd = Message::NoCarbon; - - Jid fromJid = Jid(e1.attribute(QLatin1String("from"))); - // Check for Carbon - QDomNodeList list = e1.childNodes(); - for (int i = 0; i < list.size(); ++i) { - QDomElement el = list.at(i).toElement(); - - if (el.namespaceURI() == QLatin1String("urn:xmpp:carbons:2") - && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent")) - && fromJid.compare(Jid(e1.attribute(QLatin1String("to"))), false)) { - QDomElement el1 = el.firstChildElement(); - if (el1.tagName() == QLatin1String("forwarded") - && el1.namespaceURI() == QLatin1String("urn:xmpp:forward:0")) { - QDomElement el2 = el1.firstChildElement(QLatin1String("message")); - if (!el2.isNull()) { - forward = el2; - cd = el.tagName() == QLatin1String("received") ? Message::Received : Message::Sent; - break; - } - } - } else if (el.tagName() == QLatin1String("forwarded") - && el.namespaceURI() == QLatin1String("urn:xmpp:forward:0")) { - forward = el.firstChildElement(QLatin1String("message")); // currently only messages are supportted - // TODO element support - if (!forward.isNull()) { - break; - } - } - } + if (processXmlSubscribers(e1, client(), false)) + return true; - Stanza s = client()->stream().createStanza(addCorrectNS(forward.isNull() ? e1 : forward)); + Stanza s = client()->stream().createStanza(addCorrectNS(e1)); if (s.isNull()) { // printf("take: bad stanza??\n"); return false; @@ -891,10 +983,9 @@ bool JT_PushMessage::take(const QDomElement &e) // printf("bad message\n"); return false; } - if (!forward.isNull()) { - m.setForwardedFrom(fromJid); - m.setCarbonDirection(cd); - } + + if (processMessageSubscribers(m, false)) + return true; // See: XEP-0380: Explicit Message Encryption const bool wasEncrypted = !e1.firstChildElement("encryption").isNull(); @@ -1793,41 +1884,3 @@ bool JT_CaptchaSender::take(const QDomElement &x) return true; } - -//---------------------------------------------------------------------------- -// JT_MessageCarbons -//---------------------------------------------------------------------------- -JT_MessageCarbons::JT_MessageCarbons(Task *parent) : Task(parent) { } - -void JT_MessageCarbons::enable() -{ - _iq = createIQ(doc(), "set", "", id()); - - QDomElement enable = doc()->createElementNS("urn:xmpp:carbons:2", "enable"); - - _iq.appendChild(enable); -} - -void JT_MessageCarbons::disable() -{ - _iq = createIQ(doc(), "set", "", id()); - - QDomElement disable = doc()->createElementNS("urn:xmpp:carbons:2", "disable"); - - _iq.appendChild(disable); -} - -void JT_MessageCarbons::onGo() -{ - send(_iq); - setSuccess(); -} - -bool JT_MessageCarbons::take(const QDomElement &e) -{ - if (e.tagName() != "iq" || e.attribute("type") != "result") - return false; - - bool res = iqVerify(e, Jid(), id()); - return res; -} diff --git a/src/xmpp/xmpp-im/xmpp_tasks.h b/src/xmpp/xmpp-im/xmpp_tasks.h index d500d3a1..da3ecdec 100644 --- a/src/xmpp/xmpp-im/xmpp_tasks.h +++ b/src/xmpp/xmpp-im/xmpp_tasks.h @@ -200,9 +200,22 @@ class JT_Message : public Task { class JT_PushMessage : public Task { Q_OBJECT public: + class Subscriber { + public: + virtual ~Subscriber(); + virtual bool xmlEvent(const QDomElement &root, QDomElement &e, Client *c, int userData, bool nested); + virtual bool messageEvent(Message &msg, int userData, bool nested); + }; JT_PushMessage(Task *parent, EncryptionHandler *encryptionHandler = nullptr); ~JT_PushMessage(); + void subscribeXml(Subscriber *sbs, const QString &tagName, const QString &xmlnsStr, int userData); + void unsubscribeXml(Subscriber *sbs, const QString &tagName, const QString &xmlnsStr); + void subscribeMessage(Subscriber *sbs, int userData); + void unsubscribeMessage(Subscriber *sbs); + bool processXmlSubscribers(QDomElement &el, Client *client, bool nested); + bool processMessageSubscribers(Message &msg, bool nested); + bool take(const QDomElement &); signals: @@ -210,7 +223,7 @@ class JT_PushMessage : public Task { private: class Private; - Private *d = nullptr; + std::unique_ptr d; }; class JT_VCard : public Task { @@ -407,21 +420,6 @@ class JT_PongServer : public Task { bool take(const QDomElement &); }; -class JT_MessageCarbons : public Task { - Q_OBJECT - -public: - JT_MessageCarbons(Task *parent); - void enable(); - void disable(); - - void onGo(); - bool take(const QDomElement &e); - -private: - QDomElement _iq; -}; - class JT_CaptchaChallenger : public Task { Q_OBJECT public: