diff --git a/doc/doap.xml b/doc/doap.xml index b3d2c4e07..bd6e69012 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -622,6 +622,14 @@ SPDX-License-Identifier: CC0-1.0 1.5 + + + + complete + 0.1 + 1.5 + + 1.4.0 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37e62c410..35e326dd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,6 +84,7 @@ set(INSTALL_HEADER_FILES base/QXmppSocks.h base/QXmppStanza.h base/QXmppStartTlsPacket.h + base/QXmppStickerPackItem.h base/QXmppStream.h base/QXmppStreamFeatures.h base/QXmppStun.h @@ -212,6 +213,7 @@ set(SOURCE_FILES base/QXmppRpcIq.cpp base/QXmppSasl.cpp base/QXmppSessionIq.cpp + base/QXmppStickerPackItem.cpp base/QXmppSocks.cpp base/QXmppStanza.cpp base/QXmppStartTlsPacket.cpp diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp index d0dfa3dd9..da5fc3a3d 100644 --- a/src/base/QXmppConstants.cpp +++ b/src/base/QXmppConstants.cpp @@ -204,5 +204,7 @@ const char *ns_file_metadata = "urn:xmpp:file:metadata:0"; const char *ns_sfs = "urn:xmpp:sfs:0"; // XEP-0448: Encryption for stateless file sharing const char *ns_esfs = "urn:xmpp:esfs:0"; +// XEP-0449: Stickers +const char *ns_stickers = "urn:xmpp:stickers:0"; // XEP-0450: Automatic Trust Management (ATM) const char *ns_atm = "urn:xmpp:atm:1"; diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h index ac6c18d89..e7f7a8ec8 100644 --- a/src/base/QXmppConstants_p.h +++ b/src/base/QXmppConstants_p.h @@ -216,6 +216,8 @@ extern const char *ns_file_metadata; extern const char *ns_sfs; // XEP-0448: Encryption for stateless file sharing extern const char *ns_esfs; +// XEP-0449: Stickers +extern const char *ns_stickers; // XEP-0450: Automatic Trust Management (ATM) extern const char *ns_atm; diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp index 7998598c9..0d51d6ee6 100644 --- a/src/base/QXmppMessage.cpp +++ b/src/base/QXmppMessage.cpp @@ -156,6 +156,9 @@ class QXmppMessagePrivate : public QSharedData // XEP-0448: Encryption for stateless file sharing QVector sharedFiles; + + // XEP-0449: Stickers + std::optional stickerPackId; }; QXmppMessagePrivate::QXmppMessagePrivate() @@ -1258,6 +1261,30 @@ void QXmppMessage::setSharedFiles(const QVector &sharedFiles) d->sharedFiles = sharedFiles; } +/// +/// \brief Returns the sticker pack id for the sticker contained in this message +/// +/// This is used for \xep{0449, Stickers}. +/// +/// \since QXmpp 1.5 +/// +const std::optional &QXmppMessage::stickerPackId() const +{ + return d->stickerPackId; +} + +/// +/// \brief Sets the sticker pack id for the sticker contained in this message +/// +/// This is used for \xep{0449, Stickers}. +/// +/// \since QXmpp 1.5 +/// +void QXmppMessage::setStickerPackId(const std::optional &stickerPackId) +{ + d->stickerPackId = stickerPackId; +} + /// \cond void QXmppMessage::parse(const QDomElement &element) { @@ -1552,6 +1579,12 @@ bool QXmppMessage::parseExtension(const QDomElement &element, QXmpp::SceMode sce } return true; } + + // XEP-0449: Stickers + if (checkElement(element, QStringLiteral("sticker"), ns_stickers)) { + d->stickerPackId = element.attribute("pack"); + return true; + } } return false; } @@ -1804,5 +1837,13 @@ void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode for (const auto &fileShare : d->sharedFiles) { fileShare.toXml(writer); } + + // XEP-0449: Sticker + if (d->stickerPackId) { + writer->writeStartElement("sticker"); + writer->writeDefaultNamespace(ns_stickers); + writer->writeAttribute("pack", *d->stickerPackId); + writer->writeEndElement(); + } } } diff --git a/src/base/QXmppMessage.h b/src/base/QXmppMessage.h index 899f477ed..a0cb358c1 100644 --- a/src/base/QXmppMessage.h +++ b/src/base/QXmppMessage.h @@ -257,6 +257,10 @@ class QXMPP_EXPORT QXmppMessage : public QXmppStanza const QVector &sharedFiles() const; void setSharedFiles(const QVector &sharedFiles); + // XEP-0449: Stickers + const std::optional &stickerPackId() const; + void setStickerPackId(const std::optional &stickerPackId); + /// \cond #ifdef BUILD_OMEMO // XEP-0384: OMEMO Encryption diff --git a/src/base/QXmppPubSubItem.cpp b/src/base/QXmppPubSubItem.cpp index f87c5d6ee..fba4ea801 100644 --- a/src/base/QXmppPubSubItem.cpp +++ b/src/base/QXmppPubSubItem.cpp @@ -62,12 +62,12 @@ QXmppPubSubItem::QXmppPubSubItem(const QString &id, const QString &publisher) /// Default copy-constructor QXmppPubSubItem::QXmppPubSubItem(const QXmppPubSubItem &iq) = default; /// Default move-constructor -QXmppPubSubItem::QXmppPubSubItem(QXmppPubSubItem &&) = default; +QXmppPubSubItem::QXmppPubSubItem(QXmppPubSubItem &&) noexcept = default; QXmppPubSubItem::~QXmppPubSubItem() = default; /// Default assignment operator QXmppPubSubItem &QXmppPubSubItem::operator=(const QXmppPubSubItem &iq) = default; /// Default move-assignment operator -QXmppPubSubItem &QXmppPubSubItem::operator=(QXmppPubSubItem &&iq) = default; +QXmppPubSubItem &QXmppPubSubItem::operator=(QXmppPubSubItem &&iq) noexcept = default; /// /// Returns the ID of the PubSub item. diff --git a/src/base/QXmppPubSubItem.h b/src/base/QXmppPubSubItem.h index e585de9d4..a8058a6cd 100644 --- a/src/base/QXmppPubSubItem.h +++ b/src/base/QXmppPubSubItem.h @@ -20,11 +20,11 @@ class QXMPP_EXPORT QXmppPubSubItem public: QXmppPubSubItem(const QString &id = {}, const QString &publisher = {}); QXmppPubSubItem(const QXmppPubSubItem &); - QXmppPubSubItem(QXmppPubSubItem &&); + QXmppPubSubItem(QXmppPubSubItem &&) noexcept; virtual ~QXmppPubSubItem(); QXmppPubSubItem &operator=(const QXmppPubSubItem &); - QXmppPubSubItem &operator=(QXmppPubSubItem &&); + QXmppPubSubItem &operator=(QXmppPubSubItem &&) noexcept; QString id() const; void setId(const QString &id); diff --git a/src/base/QXmppStickerPackItem.cpp b/src/base/QXmppStickerPackItem.cpp new file mode 100644 index 000000000..215dbaa1b --- /dev/null +++ b/src/base/QXmppStickerPackItem.cpp @@ -0,0 +1,278 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppStickerPackItem.h" + +#include "QXmppConstants_p.h" +#include "QXmppEncryptedFileSource.h" +#include "QXmppFileMetadata.h" + +#include + +class QXmppStickerItemPrivate : public QSharedData +{ +public: + QXmppFileMetadata metadata; + QVector httpSources; + QVector encryptedSources; + QVector suggest; +}; + +QXmppStickerItem::QXmppStickerItem() + : d(new QXmppStickerItemPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppStickerItem) + +/// +/// \class QXmppStickerItem +/// +/// This class represents a single sticker when publishing or retrieving it. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \brief Returns metadata about the sticker file +/// +const QXmppFileMetadata &QXmppStickerItem::metadata() const +{ + return d->metadata; +} + +/// +/// \brief Sets metadata of this sticker file +/// +void QXmppStickerItem::setMetadata(const QXmppFileMetadata &metadata) +{ + d->metadata = metadata; +} + +/// +/// \brief Returns HTTP sources for the sticker file +/// +const QVector &QXmppStickerItem::httpSource() const +{ + return d->httpSources; +} + +/// +/// \brief Sets the list of HTTP sources for this sticker file +/// +void QXmppStickerItem::setHttpSources(const QVector &httpSources) +{ + d->httpSources = httpSources; +} + +/// +/// \brief Returns the list of encrypted sources for this sticker file +/// +const QVector &QXmppStickerItem::encryptedSources() const +{ + return d->encryptedSources; +} + +/// +/// \brief Set the list of encrypted sources for this sticker file +/// +void QXmppStickerItem::setEncryptedSources(const QVector &encryptedSources) +{ + d->encryptedSources = encryptedSources; +} + +/// +/// \brief Returns words for which apps can suggest the use of this sticker +/// +const QVector &QXmppStickerItem::suggestedWords() const +{ + return d->suggest; +} + +/// +/// \brief Sets the words for which apps can suggest the use of this sticker +/// +void QXmppStickerItem::setSuggestedWords(const QVector &suggest) +{ + d->suggest = suggest; +} + +/// \cond +void QXmppStickerItem::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("item"); + d->metadata.toXml(writer); + writer->writeStartElement("sources"); + writer->writeDefaultNamespace(ns_sfs); + for (const auto &httpSource : d->httpSources) { + httpSource.toXml(writer); + } + for (const auto &encryptedSource : d->encryptedSources) { + encryptedSource.toXml(writer); + } + writer->writeEndElement(); + for (const auto &word : d->suggest) { + writer->writeTextElement("suggest", word); + } + writer->writeEndElement(); +} + +bool QXmppStickerItem::parse(const QDomElement &element) +{ + auto fileElement = element.firstChildElement("file"); + d->metadata.parse(fileElement); + + auto sources = element.firstChildElement("sources"); + for (auto sourceEl = sources.firstChildElement(); + !sourceEl.isNull(); + sourceEl = sourceEl.nextSiblingElement()) { + if (sourceEl.tagName() == QStringLiteral("url-data")) { + QXmppHttpFileSource source; + if (source.parse(sourceEl)) { + d->httpSources.push_back(std::move(source)); + } + } else if (sourceEl.tagName() == QStringLiteral("encrypted")) { + QXmppEncryptedFileSource source; + if (source.parse(sourceEl)) { + d->encryptedSources.push_back(std::move(source)); + } + } + } + + for (auto suggestEl = element.firstChildElement("suggest"); + !suggestEl.isNull(); + suggestEl = suggestEl.nextSiblingElement("suggest")) { + d->suggest.push_back(suggestEl.text()); + } + + return true; +} +/// \endcond + +class QXmppStickerPackItemPrivate : public QSharedData +{ +public: + QString name; + QString summary; + QVector items; + bool restricted = false; + QXmppHash hash; +}; + +QXmppStickerPackItem::QXmppStickerPackItem() + : d(new QXmppStickerPackItemPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppStickerPackItem) + +/// +/// \class QXmppStickerPackitem +/// +/// A pubsub item that represents a sticker pack. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \brief Returns the name of the sticker pack +/// +const QString &QXmppStickerPackItem::name() const +{ + return d->name; +} + +/// +/// \brief Sets the name of the sticker pack +/// +void QXmppStickerPackItem::setName(const QString &name) +{ + d->name = name; +} + +/// +/// \brief Returns the summary of this sticker pack +/// +const QString &QXmppStickerPackItem::summary() const +{ + return d->summary; +} + +/// +/// \brief Sets the summary of the sticker pack +/// +void QXmppStickerPackItem::setSummary(const QString &summary) +{ + d->summary = summary; +} + +/// +/// \brief Returns the list of stickers of this pack +/// +const QVector &QXmppStickerPackItem::items() const +{ + return d->items; +} + +/// +/// \brief Set the list of stickers for this pack +/// +void QXmppStickerPackItem::setItems(const QVector &items) +{ + d->items = items; +} + +/// +/// \brief Returns whether this sticker pack can be freely imported +/// +bool QXmppStickerPackItem::restricted() const +{ + return d->restricted; +} + +/// +/// \brief Set whether this sticker pack should be importable by others +/// +void QXmppStickerPackItem::setRestricted(bool restricted) +{ + d->restricted = restricted; +} + +void QXmppStickerPackItem::parsePayload(const QDomElement &payloadElement) +{ + d->name = payloadElement.firstChildElement("name").text(); + d->summary = payloadElement.firstChildElement("summary").text(); + + for (auto firstChild = payloadElement.firstChildElement("item"); + !firstChild.isNull(); + firstChild = firstChild.nextSiblingElement("item")) { + QXmppStickerItem stickerItem; + stickerItem.parse(firstChild); + + d->items.push_back(std::move(stickerItem)); + } + + d->hash.parse(payloadElement.firstChildElement("hash")); + d->restricted = !payloadElement.firstChildElement("restricted").isNull(); +} + +void QXmppStickerPackItem::serializePayload(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("pack"); + writer->writeDefaultNamespace(ns_stickers); + + writer->writeTextElement("name", d->name); + writer->writeTextElement("summary", d->summary); + + for (const auto &item : d->items) { + item.toXml(writer); + } + + d->hash.toXml(writer); + + if (d->restricted) { + writer->writeEmptyElement("restricted"); + } + writer->writeEndElement(); +} diff --git a/src/base/QXmppStickerPackItem.h b/src/base/QXmppStickerPackItem.h new file mode 100644 index 000000000..4fd18b0ec --- /dev/null +++ b/src/base/QXmppStickerPackItem.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPSTICKERPACKITEM_H +#define QXMPPSTICKERPACKITEM_H + +#include "QXmppPubSubItem.h" + +#include + +#include + +class QXmppFileMetadata; +class QXmppHttpFileSource; +class QXmppEncryptedFileSource; + +class QXmppStickerItemPrivate; + +class QXMPP_EXPORT QXmppStickerItem +{ +public: + QXmppStickerItem(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppStickerItem) + + const QXmppFileMetadata &metadata() const; + void setMetadata(const QXmppFileMetadata &metadata); + + const QVector &httpSource() const; + void setHttpSources(const QVector &httpSources); + + const QVector &encryptedSources() const; + void setEncryptedSources(const QVector &encryptedSources); + + const QVector &suggestedWords() const; + void setSuggestedWords(const QVector &suggest); + + /// \cond + bool parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QSharedDataPointer d; +}; + +class QXmppStickerPackItemPrivate; + +class QXMPP_EXPORT QXmppStickerPackItem : public QXmppPubSubItem +{ +public: + QXmppStickerPackItem(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppStickerPackItem) + + const QString &name() const; + void setName(const QString &name); + + const QString &summary() const; + void setSummary(const QString &summary); + + const QVector &items() const; + void setItems(const QVector &items); + + bool restricted() const; + void setRestricted(bool restricted); + + void parsePayload(const QDomElement &payloadElement) override; + void serializePayload(QXmlStreamWriter *writer) const override; + +private: + QSharedDataPointer d; +}; + +#endif // QXMPPSTICKERPACKITEM_H diff --git a/tests/qxmppmessage/tst_qxmppmessage.cpp b/tests/qxmppmessage/tst_qxmppmessage.cpp index 1aa2c6735..3c0848953 100644 --- a/tests/qxmppmessage/tst_qxmppmessage.cpp +++ b/tests/qxmppmessage/tst_qxmppmessage.cpp @@ -56,6 +56,7 @@ private slots: void testE2eeFallbackBody(); void testFileSharing(); void testEncryptedFileSource(); + void testStickers(); }; void tst_QXmppMessage::testBasic_data() @@ -1259,5 +1260,32 @@ void tst_QXmppMessage::testEncryptedFileSource() } } +void tst_QXmppMessage::testStickers() +{ + QByteArray xml( + "" + "😘" + "" + "" + "😘" + "gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=" + "image/png" + "67016" + "" + "" + "" + "" + "" + "" + ""); + + QXmppMessage message1; + parsePacket(message1, xml); + QVERIFY(!message1.sharedFiles().empty()); + QVERIFY(message1.stickerPackId().has_value()); + QCOMPARE(message1.stickerPackId().value(), QStringLiteral("EpRv28DHHzFrE4zd+xaNpVb4")); + serializePacket(message1, xml); +} + QTEST_MAIN(tst_QXmppMessage) #include "tst_qxmppmessage.moc" diff --git a/tests/qxmpppubsub/tst_qxmpppubsub.cpp b/tests/qxmpppubsub/tst_qxmpppubsub.cpp index b34f34cfd..fc5d931f0 100644 --- a/tests/qxmpppubsub/tst_qxmpppubsub.cpp +++ b/tests/qxmpppubsub/tst_qxmpppubsub.cpp @@ -2,8 +2,10 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later +#include "QXmppHttpFileSource.h" #include "QXmppPubSubAffiliation.h" #include "QXmppPubSubSubscription.h" +#include "QXmppStickerPackItem.h" #include "pubsubutil.h" #include "util.h" @@ -55,6 +57,7 @@ class tst_QXmppPubSub : public QObject Q_SLOT void testIsItem_data(); Q_SLOT void testIsItem(); Q_SLOT void testTestItem(); + Q_SLOT void testStickerPackItem(); }; void tst_QXmppPubSub::testAffiliation_data() @@ -280,5 +283,57 @@ void tst_QXmppPubSub::testTestItem() QVERIFY(!TestItem::isItem(xmlToDom(invalidXml))); } +void tst_QXmppPubSub::testStickerPackItem() +{ + QByteArray xml( + "" + "" + "Marsey the Cat" + "Be cute or be cynical, this little kitten works both ways." + "" + "" + "👍" + "0AdP8lJOWJrugSKOIAqfEKqFatIpG5JBCjjxY253ojQ=" + "512" + "image/png" + "71045" + "512" + "" + "" + "" + "" + "+1" + "thumbsup" + "" + "" + "" + "😘" + "gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=" + "512" + "image/png" + "67016" + "512" + "" + "" + "" + "" + "" + "EpRv28DHHzFrE4zd+xaNpVb4jbu4s74XtioExNjQzZ0=" + "" + ""); + + QXmppStickerPackItem item; + parsePacket(item, xml); + QCOMPARE(item.items().size(), 2); + QCOMPARE(item.name(), QStringLiteral("Marsey the Cat")); + QCOMPARE(item.summary(), QStringLiteral("Be cute or be cynical, this little kitten works both ways.")); + QVERIFY(!item.restricted()); + + auto &firstItem = item.items().front(); + QCOMPARE(firstItem.suggestedWords().size(), 2); + QCOMPARE(firstItem.httpSource().front().url(), QUrl("https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_thumbs_up.png")); + serializePacket(item, xml); +} + QTEST_MAIN(tst_QXmppPubSub) #include "tst_qxmpppubsub.moc"