// SPDX-FileCopyrightText: 2009 Manjeet Dahiya // SPDX-FileCopyrightText: 2010 Jeremy Lainé // SPDX-FileCopyrightText: 2018 Linus Jahn // SPDX-FileCopyrightText: 2021 Melvin Keskin // // SPDX-License-Identifier: LGPL-2.1-or-later #include "QXmppMessage.h" #include "QXmppBitsOfBinaryDataList.h" #include "QXmppConstants_p.h" #include "QXmppGlobal_p.h" #include "QXmppMixInvitation.h" #include "QXmppOmemoElement_p.h" #include "QXmppOmemoEnvelope_p.h" #include "QXmppTrustMessageElement.h" #include "QXmppUtils.h" #include #include #include #include static const QStringList CHAT_STATES = { QString(), QStringLiteral("active"), QStringLiteral("inactive"), QStringLiteral("gone"), QStringLiteral("composing"), QStringLiteral("paused") }; static const QStringList MESSAGE_TYPES = { QStringLiteral("error"), QStringLiteral("normal"), QStringLiteral("chat"), QStringLiteral("groupchat"), QStringLiteral("headline") }; static const QStringList MARKER_TYPES = { QString(), QStringLiteral("received"), QStringLiteral("displayed"), QStringLiteral("acknowledged") }; static const QStringList HINT_TYPES = { QStringLiteral("no-permanent-store"), QStringLiteral("no-store"), QStringLiteral("no-copy"), QStringLiteral("store") }; static bool checkElement(const QDomElement &element, const QString &tagName, const QString &xmlns) { return element.tagName() == tagName && element.namespaceURI() == xmlns; } enum StampType { LegacyDelayedDelivery, // XEP-0091: Legacy Delayed Delivery DelayedDelivery // XEP-0203: Delayed Delivery }; class QXmppMessagePrivate : public QSharedData { public: QXmppMessagePrivate(); QString body; QString e2eeFallbackBody; QString subject; QString thread; QString parentThread; QXmppMessage::Type type; // XEP-0066: Out of Band Data QString outOfBandUrl; // XEP-0071: XHTML-IM QString xhtml; // XEP-0085: Chat State Notifications QXmppMessage::State state; // XEP-0091: Legacy Delayed Delivery | XEP-0203: Delayed Delivery QDateTime stamp; StampType stampType; // XEP-0184: Message Delivery Receipts QString receiptId; bool receiptRequested; // XEP-0224: Attention bool attentionRequested; // XEP-0231: Bits of Binary QXmppBitsOfBinaryDataList bitsOfBinaryData; // XEP-0249: Direct MUC Invitations QString mucInvitationJid; QString mucInvitationPassword; QString mucInvitationReason; // XEP-0280: Message Carbons bool privatemsg; // XEP-0308: Last Message Correction QString replaceId; // XEP-0333: Chat Markers bool markable; QXmppMessage::Marker marker; QString markedId; QString markedThread; // XEP-0334: Message Processing Hints quint8 hints; // XEP-0359: Unique and Stable Stanza IDs QString stanzaId; QString stanzaIdBy; QString originId; // XEP-0367: Message Attaching QString attachId; // XEP-0369: Mediated Information eXchange (MIX) QString mixUserJid; QString mixUserNick; // XEP-0380: Explicit Message Encryption QString encryptionMethod; QString encryptionName; // XEP-0382: Spoiler messages bool isSpoiler; QString spoilerHint; // XEP-0384: OMEMO Encryption std::optional omemoElement; // XEP-0407: Mediated Information eXchange (MIX): Miscellaneous Capabilities std::optional mixInvitation; // XEP-0428: Fallback Indication bool isFallback; // XEP-0434: Trust Messages (TM) std::optional trustMessageElement; }; QXmppMessagePrivate::QXmppMessagePrivate() : type(QXmppMessage::Normal), state(QXmppMessage::None), stampType(DelayedDelivery), receiptRequested(false), attentionRequested(false), privatemsg(false), markable(false), marker(QXmppMessage::NoMarker), hints(0), isSpoiler(false), isFallback(false) { } /// Constructs a QXmppMessage. /// /// \param from /// \param to /// \param body /// \param thread QXmppMessage::QXmppMessage(const QString &from, const QString &to, const QString &body, const QString &thread) : QXmppStanza(from, to), d(new QXmppMessagePrivate) { d->type = Chat; d->body = body; d->thread = thread; } /// Constructs a copy of \a other. QXmppMessage::QXmppMessage(const QXmppMessage &other) = default; QXmppMessage::~QXmppMessage() = default; /// Assigns \a other to this message. QXmppMessage &QXmppMessage::operator=(const QXmppMessage &other) = default; /// /// Indicates if the QXmppStanza is a stanza in the XMPP sense (i. e. a message, /// iq or presence) /// /// \since QXmpp 1.0 /// bool QXmppMessage::isXmppStanza() const { return true; } /// Returns the message's body. QString QXmppMessage::body() const { return d->body; } /// Sets the message's body. /// /// \param body void QXmppMessage::setBody(const QString &body) { d->body = body; } /// /// Returns a body that is unlike the normal body not encrypted. /// /// It can be presented to users if the message could not be decrypted (e.g., /// because their clients do not support the used end-to-end encryption). /// /// \return the end-to-end encryption fallback body /// /// \since QXmpp 1.5 /// QString QXmppMessage::e2eeFallbackBody() const { return d->e2eeFallbackBody; } /// /// Sets a body that is unlike the normal body not encrypted. /// /// It can be presented to users if the message could not be decrypted (e.g., /// because their clients do not support the used end-to-end encryption). /// /// \param fallbackBody end-to-end encryption fallback body /// /// \since QXmpp 1.5 /// void QXmppMessage::setE2eeFallbackBody(const QString &fallbackBody) { d->e2eeFallbackBody = fallbackBody; } /// Returns the message's type. QXmppMessage::Type QXmppMessage::type() const { return d->type; } /// Sets the message's type. /// /// \param type void QXmppMessage::setType(QXmppMessage::Type type) { d->type = type; } /// Returns the message's subject. QString QXmppMessage::subject() const { return d->subject; } /// Sets the message's subject. /// /// \param subject void QXmppMessage::setSubject(const QString &subject) { d->subject = subject; } /// Returns the message's thread. QString QXmppMessage::thread() const { return d->thread; } /// Sets the message's thread. /// /// \param thread void QXmppMessage::setThread(const QString &thread) { d->thread = thread; } /// /// Returns the optional parent thread of this message. /// /// The possibility to create child threads was added in RFC6121. /// /// \since QXmpp 1.3 /// QString QXmppMessage::parentThread() const { return d->parentThread; } /// /// Sets the optional parent thread of this message. /// /// The possibility to create child threads was added in RFC6121. /// /// \since QXmpp 1.3 /// void QXmppMessage::setParentThread(const QString &parent) { d->parentThread = parent; } /// /// Returns a possibly attached URL from \xep{0066}: Out of Band Data /// /// \since QXmpp 1.0 /// QString QXmppMessage::outOfBandUrl() const { return d->outOfBandUrl; } /// /// Sets the attached URL for \xep{0066}: Out of Band Data /// /// \since QXmpp 1.0 /// void QXmppMessage::setOutOfBandUrl(const QString &url) { d->outOfBandUrl = url; } /// /// Returns the message's XHTML body as defined by \xep{0071}: XHTML-IM. /// /// \since QXmpp 0.6.2 /// QString QXmppMessage::xhtml() const { return d->xhtml; } /// /// Sets the message's XHTML body as defined by \xep{0071}: XHTML-IM. /// /// \since QXmpp 0.6.2 /// void QXmppMessage::setXhtml(const QString &xhtml) { d->xhtml = xhtml; } /// /// Returns the the chat state notification according to \xep{0085}: Chat State /// Notifications. /// /// \since QXmpp 0.2 /// QXmppMessage::State QXmppMessage::state() const { return d->state; } /// /// Sets the the chat state notification according to \xep{0085}: Chat State /// Notifications. /// /// \since QXmpp 0.2 /// void QXmppMessage::setState(QXmppMessage::State state) { d->state = state; } /// /// Returns the optional timestamp of the message specified using \xep{0093}: /// Legacy Delayed Delivery or using \xep{0203}: Delayed Delivery (preferred). /// /// \since QXmpp 0.2 /// QDateTime QXmppMessage::stamp() const { return d->stamp; } /// /// Sets the message's timestamp without modifying the type of the stamp /// (\xep{0093}: Legacy Delayed Delivery or \xep{0203}: Delayed Delivery). /// /// By default messages are constructed with the new delayed delivery XEP, but /// parsed messages keep their type. /// /// \since QXmpp 0.2 /// void QXmppMessage::setStamp(const QDateTime &stamp) { d->stamp = stamp; } /// /// Returns true if a delivery receipt is requested, as defined by \xep{0184}: /// Message Delivery Receipts. /// /// \since QXmpp 0.4 /// bool QXmppMessage::isReceiptRequested() const { return d->receiptRequested; } /// /// Sets whether a delivery receipt is requested, as defined by \xep{0184}: /// Message Delivery Receipts. /// /// \since QXmpp 0.4 /// void QXmppMessage::setReceiptRequested(bool requested) { d->receiptRequested = requested; if (requested && id().isEmpty()) generateAndSetNextId(); } /// /// If this message is a delivery receipt, returns the ID of the original /// message. /// /// \since QXmpp 0.4 /// QString QXmppMessage::receiptId() const { return d->receiptId; } /// /// Make this message a delivery receipt for the message with the given \a id. /// /// \since QXmpp 0.4 /// void QXmppMessage::setReceiptId(const QString &id) { d->receiptId = id; } /// /// Returns true if the user's attention is requested, as defined by \xep{0224}: /// Attention. /// /// \since QXmpp 0.4 /// bool QXmppMessage::isAttentionRequested() const { return d->attentionRequested; } /// /// Sets whether the user's attention is requested, as defined by \xep{0224}: /// Attention. /// /// \param requested Whether to request attention (true) or not (false) /// /// \since QXmpp 0.4 /// void QXmppMessage::setAttentionRequested(bool requested) { d->attentionRequested = requested; } /// /// Returns a list of data packages attached using \xep{0231}: Bits of Binary. /// /// This could be used to resolve \c cid: URIs found in the X-HTML body. /// /// \since QXmpp 1.2 /// QXmppBitsOfBinaryDataList QXmppMessage::bitsOfBinaryData() const { return d->bitsOfBinaryData; } /// /// Returns a list of data attached using \xep{0231}: Bits of Binary. /// /// This could be used to resolve \c cid: URIs found in the X-HTML body. /// /// \since QXmpp 1.2 /// QXmppBitsOfBinaryDataList &QXmppMessage::bitsOfBinaryData() { return d->bitsOfBinaryData; } /// /// Sets a list of \xep{0231}: Bits of Binary attachments to be included. /// /// \since QXmpp 1.2 /// void QXmppMessage::setBitsOfBinaryData(const QXmppBitsOfBinaryDataList &bitsOfBinaryData) { d->bitsOfBinaryData = bitsOfBinaryData; } /// /// Returns whether the given text is a '/me command' as defined in \xep{0245}: /// The /me Command. /// /// \since QXmpp 1.3 /// bool QXmppMessage::isSlashMeCommand(const QString &body) { return body.startsWith(QStringLiteral("/me ")); } /// /// Returns whether the body of the message is a '/me command' as defined in /// \xep{0245}: The /me Command. /// /// \note If you want to check a custom string for the /me command, you can use /// the static version of this method. This can be helpful when checking user /// input before a message was sent. /// /// \since QXmpp 1.3 /// bool QXmppMessage::isSlashMeCommand() const { return isSlashMeCommand(d->body); } /// /// Returns the part of the body after the /me command. /// /// This cuts off '/me ' (with the space) from the body, in case the body /// starts with that. In case the body does not contain a /me command as /// defined in \xep{0245}: The /me Command, a null string is returned. /// /// This is useful when displaying the /me command correctly to the user. /// /// \since QXmpp 1.3 /// QString QXmppMessage::slashMeCommandText(const QString &body) { if (isSlashMeCommand(body)) return body.mid(4); return {}; } /// /// Returns the part of the body after the /me command. /// /// This cuts off '/me ' (with the space) from the body, in case the body /// starts with that. In case the body does not contain a /me command as /// defined in \xep{0245}: The /me Command, a null string is returned. /// /// This is useful when displaying the /me command correctly to the user. /// /// \since QXmpp 1.3 /// QString QXmppMessage::slashMeCommandText() const { return slashMeCommandText(d->body); } /// /// Returns the JID for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// QString QXmppMessage::mucInvitationJid() const { return d->mucInvitationJid; } /// /// Sets the JID for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// void QXmppMessage::setMucInvitationJid(const QString &jid) { d->mucInvitationJid = jid; } /// /// Returns the password for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// QString QXmppMessage::mucInvitationPassword() const { return d->mucInvitationPassword; } /// /// Sets the \a password for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// void QXmppMessage::setMucInvitationPassword(const QString &password) { d->mucInvitationPassword = password; } /// /// Returns the reason for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// QString QXmppMessage::mucInvitationReason() const { return d->mucInvitationReason; } /// /// Sets the \a reason for a multi-user chat direct invitation as defined by /// \xep{0249}: Direct MUC Invitations. /// /// \since QXmpp 0.7.4 /// void QXmppMessage::setMucInvitationReason(const QString &reason) { d->mucInvitationReason = reason; } /// /// Returns if the message is marked with a <private/> tag, in which case /// it will not be forwarded to other resources according to \xep{0280}: Message /// Carbons. /// /// \since QXmpp 1.0 /// bool QXmppMessage::isPrivate() const { return d->privatemsg; } /// /// If true is passed, the message is marked with a <private/> tag, in /// which case it will not be forwarded to other resources according to /// \xep{0280}: Message Carbons. /// /// \since QXmpp 1.0 /// void QXmppMessage::setPrivate(const bool priv) { d->privatemsg = priv; } /// /// Returns the message id to replace with this message as used in \xep{0308}: /// Last Message Correction. If the returned string is empty, this message is /// not replacing another. /// /// \since QXmpp 1.0 /// QString QXmppMessage::replaceId() const { return d->replaceId; } /// /// Sets the message id to replace with this message as in \xep{0308}: Last /// Message Correction. /// /// \since QXmpp 1.0 /// void QXmppMessage::setReplaceId(const QString &replaceId) { d->replaceId = replaceId; } /// /// Returns true if a message is markable, as defined by \xep{0333}: Chat /// Markers. /// /// \since QXmpp 0.8.1 /// bool QXmppMessage::isMarkable() const { return d->markable; } /// /// Sets if the message is markable, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// void QXmppMessage::setMarkable(const bool markable) { d->markable = markable; } /// /// Returns the message's marker id, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// QString QXmppMessage::markedId() const { return d->markedId; } /// /// Sets the message's marker id, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// void QXmppMessage::setMarkerId(const QString &markerId) { d->markedId = markerId; } /// /// Returns the message's marker thread, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// QString QXmppMessage::markedThread() const { return d->markedThread; } /// /// Sets the message's marked thread, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// void QXmppMessage::setMarkedThread(const QString &markedThread) { d->markedThread = markedThread; } /// /// Returns the message's marker, as defined by \xep{0333}: Chat Markers. /// /// \since QXmpp 0.8.1 /// QXmppMessage::Marker QXmppMessage::marker() const { return d->marker; } /// /// Sets the message's marker, as defined by \xep{0333}: Chat Markers /// /// \since QXmpp 0.8.1 /// void QXmppMessage::setMarker(const Marker marker) { d->marker = marker; } /// /// Returns true if the message contains the hint passed, as defined in /// \xep{0334}: Message Processing Hints /// /// \since QXmpp 1.1 /// bool QXmppMessage::hasHint(const Hint hint) const { return d->hints & hint; } /// /// Adds a hint to the message, as defined in \xep{0334}: Message Processing /// Hints /// /// \since QXmpp 1.1 /// void QXmppMessage::addHint(const Hint hint) { d->hints |= hint; } /// /// Removes a hint from the message, as defined in \xep{0334}: Message /// Processing Hints /// /// \since QXmpp 1.1 /// void QXmppMessage::removeHint(const Hint hint) { d->hints &= ~hint; } /// /// Removes all hints from the message, as defined in \xep{0334}: Message /// Processing Hints /// /// \since QXmpp 1.1 /// void QXmppMessage::removeAllHints() { d->hints = 0; } /// /// Returns the stanza ID of the message according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// QString QXmppMessage::stanzaId() const { return d->stanzaId; } /// /// Sets the stanza ID of the message according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// void QXmppMessage::setStanzaId(const QString &id) { d->stanzaId = id; } /// /// Returns the creator of the stanza ID according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// QString QXmppMessage::stanzaIdBy() const { return d->stanzaIdBy; } /// /// Sets the creator of the stanza ID according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// void QXmppMessage::setStanzaIdBy(const QString &by) { d->stanzaIdBy = by; } /// /// Returns the origin ID of the message according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// QString QXmppMessage::originId() const { return d->originId; } /// /// Sets the origin ID of the message according to \xep{0359}: Unique and /// Stable Stanza IDs. /// /// \since QXmpp 1.3 /// void QXmppMessage::setOriginId(const QString &id) { d->originId = id; } /// /// Returns the message id this message is linked/attached to. See \xep{0367}: /// Message Attaching for details. /// /// \since QXmpp 1.1 /// QString QXmppMessage::attachId() const { return d->attachId; } /// /// Sets the id of the attached message as in \xep{0367}: Message Attaching. /// This can be used for a "reply to" or "reaction" function. /// /// The used message id depends on the message context, see the Business rules /// section of the XEP for details about when to use which id. /// /// \since QXmpp 1.1 /// void QXmppMessage::setAttachId(const QString &attachId) { d->attachId = attachId; } /// /// Returns the actual JID of a MIX channel participant. /// /// \since QXmpp 1.1 /// QString QXmppMessage::mixUserJid() const { return d->mixUserJid; } /// /// Sets the actual JID of a MIX channel participant. /// /// \since QXmpp 1.1 /// void QXmppMessage::setMixUserJid(const QString &mixUserJid) { d->mixUserJid = mixUserJid; } /// /// Returns the MIX participant's nickname. /// /// \since QXmpp 1.1 /// QString QXmppMessage::mixUserNick() const { return d->mixUserNick; } /// /// Sets the MIX participant's nickname. /// /// \since QXmpp 1.1 /// void QXmppMessage::setMixUserNick(const QString &mixUserNick) { d->mixUserNick = mixUserNick; } /// /// Returns the encryption method this message is advertised to be encrypted /// with. /// /// \note QXmppMessage::NoEncryption does not necesserily mean that the message /// is not encrypted; it may also be that the author of the message does not /// support \xep{0380}: Explicit Message Encryption. /// /// \note If this returns QXmppMessage::UnknownEncryption, you can still get /// the namespace of the encryption with \c encryptionMethodNs() and possibly /// also a name with \c encryptionName(). /// /// \since QXmpp 1.1 /// QXmpp::Encryption QXmppMessage::encryptionMethod() const { if (d->encryptionMethod.isEmpty()) { return QXmpp::NoEncryption; } return QXmpp::Private::encryptionFromString(d->encryptionMethod).value_or(QXmpp::UnknownEncryption); } /// /// Advertises that this message is encrypted with the given encryption method. /// See \xep{0380}: Explicit Message Encryption for details. /// /// \since QXmpp 1.1 /// void QXmppMessage::setEncryptionMethod(QXmpp::Encryption method) { d->encryptionMethod = QXmpp::Private::encryptionToString(method); } /// /// Returns the namespace of the advertised encryption method via. \xep{0380}: /// Explicit Message Encryption. /// /// \since QXmpp 1.1 /// QString QXmppMessage::encryptionMethodNs() const { return d->encryptionMethod; } /// /// Sets the namespace of the encryption method this message advertises to be /// encrypted with. See \xep{0380}: Explicit Message Encryption for details. /// /// \since QXmpp 1.1 /// void QXmppMessage::setEncryptionMethodNs(const QString &encryptionMethod) { d->encryptionMethod = encryptionMethod; } /// /// Returns the associated name of the encryption method this message /// advertises to be encrypted with. See \xep{0380}: Explicit Message Encryption /// for details. /// /// \since QXmpp 1.1 /// QString QXmppMessage::encryptionName() const { if (!d->encryptionName.isEmpty()) return d->encryptionName; return QXmpp::Private::encryptionToName(encryptionMethod()); } /// /// Sets the name of the encryption method for \xep{0380}: Explicit Message /// Encryption. /// /// \note This should only be used, if the encryption method is custom and is /// not one of the methods listed in the XEP. /// /// \since QXmpp 1.1 /// void QXmppMessage::setEncryptionName(const QString &encryptionName) { d->encryptionName = encryptionName; } /// /// Returns true, if this is a spoiler message according to \xep{0382}: Spoiler /// messages. The spoiler hint however can still be empty. /// /// A spoiler message's content should not be visible to the user by default. /// /// \since QXmpp 1.1 /// bool QXmppMessage::isSpoiler() const { return d->isSpoiler; } /// /// Sets whether this is a spoiler message as specified in \xep{0382}: Spoiler /// messages. /// /// The content of spoiler messages will not be displayed by default to the /// user. However, clients not supporting spoiler messages will still display /// the content as usual. /// /// \since QXmpp 1.1 /// void QXmppMessage::setIsSpoiler(bool isSpoiler) { d->isSpoiler = isSpoiler; } /// /// Returns the spoiler hint as specified in \xep{0382}: Spoiler messages. /// /// The hint may be empty, even if isSpoiler is true. /// /// \since QXmpp 1.1 /// QString QXmppMessage::spoilerHint() const { return d->spoilerHint; } /// /// Sets a spoiler hint for \xep{0382}: Spoiler messages. If the spoiler hint /// is not empty, isSpoiler will be set to true. /// /// A spoiler hint is optional for spoiler messages. /// /// Keep in mind that the spoiler hint is not displayed at all by clients not /// supporting spoiler messages. /// /// \since QXmpp 1.1 /// void QXmppMessage::setSpoilerHint(const QString &spoilerHint) { d->spoilerHint = spoilerHint; if (!spoilerHint.isEmpty()) d->isSpoiler = true; } /// /// Returns an included OMEMO element as defined by \xep{0384, OMEMO Encryption}. /// /// \since QXmpp 1.5 /// std::optional QXmppMessage::omemoElement() const { return d->omemoElement; } /// /// Sets an OMEMO element as defined by \xep{0384, OMEMO Encryption}. /// /// \since QXmpp 1.5 /// void QXmppMessage::setOmemoElement(const std::optional &omemoElement) { d->omemoElement = omemoElement; } /// /// Returns an included \xep{0369}: Mediated Information eXchange (MIX) /// invitation as defined by \xep{0407}: Mediated Information eXchange (MIX): /// Miscellaneous Capabilities. /// /// \since QXmpp 1.4 /// std::optional QXmppMessage::mixInvitation() const { return d->mixInvitation; } /// /// Sets a \xep{0369}: Mediated Information eXchange (MIX) invitation as defined /// by \xep{0407}: Mediated Information eXchange (MIX): Miscellaneous /// Capabilities. /// /// \since QXmpp 1.4 /// void QXmppMessage::setMixInvitation(const std::optional &mixInvitation) { d->mixInvitation = mixInvitation; } /// /// Sets whether this message is only a fallback according to \xep{0428}: /// Fallback Indication. /// /// This is useful for clients not supporting end-to-end encryption to indicate /// that the message body does not contain the intended text of the author. /// /// \since QXmpp 1.3 /// bool QXmppMessage::isFallback() const { return d->isFallback; } /// /// Sets whether this message is only a fallback according to \xep{0428}: /// Fallback Indication. /// /// This is useful for clients not supporting end-to-end encryption to indicate /// that the message body does not contain the intended text of the author. /// /// \since QXmpp 1.3 /// void QXmppMessage::setIsFallback(bool isFallback) { d->isFallback = isFallback; } /// /// Returns an included trust message element as defined by /// \xep{0434, Trust Messages (TM)}. /// /// \since QXmpp 1.5 /// std::optional QXmppMessage::trustMessageElement() const { return d->trustMessageElement; } /// /// Sets a trust message element as defined by \xep{0434, Trust Messages (TM)}. /// /// \since QXmpp 1.5 /// void QXmppMessage::setTrustMessageElement(const std::optional &trustMessageElement) { d->trustMessageElement = trustMessageElement; } /// \cond void QXmppMessage::parse(const QDomElement &element) { parse(element, QXmpp::SceAll); } void QXmppMessage::parse(const QDomElement &element, QXmpp::SceMode sceMode) { QXmppStanza::parse(element); // message type int messageType = MESSAGE_TYPES.indexOf(element.attribute(QStringLiteral("type"))); if (messageType != -1) d->type = static_cast(messageType); else d->type = QXmppMessage::Normal; QXmppElementList extensions; for (auto childElement = element.firstChildElement(); !childElement.isNull(); childElement = childElement.nextSiblingElement()) { if (!checkElement(childElement, QStringLiteral("addresses"), ns_extended_addressing) && childElement.tagName() != QStringLiteral("error")) { // try to parse; add to unknown extensions, if element couldn't be parsed if (!parseExtension(childElement, sceMode)) { // other extensions extensions << QXmppElement(childElement); } } } setExtensions(extensions); } void QXmppMessage::toXml(QXmlStreamWriter *writer) const { toXml(writer, QXmpp::SceAll); } void QXmppMessage::toXml(QXmlStreamWriter *writer, QXmpp::SceMode sceMode) const { writer->writeStartElement(QStringLiteral("message")); helperToXmlAddAttribute(writer, QStringLiteral("xml:lang"), lang()); helperToXmlAddAttribute(writer, QStringLiteral("id"), id()); helperToXmlAddAttribute(writer, QStringLiteral("to"), to()); helperToXmlAddAttribute(writer, QStringLiteral("from"), from()); helperToXmlAddAttribute(writer, QStringLiteral("type"), MESSAGE_TYPES.at(d->type)); error().toXml(writer); // extensions serializeExtensions(writer, sceMode); // other, unknown extensions QXmppStanza::extensionsToXml(writer); writer->writeEndElement(); } /// \endcond /// /// Parses a child element of the message stanza. /// /// Allows inherited classes to parse additional extension elements. This /// function may be executed multiple times with different elements. /// /// \param element child element of the message to be parsed /// \param sceMode Which elements to be parsed from the DOM (all known / only /// public / only sensitive) /// /// \return True, if the element was successfully parsed. /// /// \since QXmpp 1.5 /// bool QXmppMessage::parseExtension(const QDomElement &element, QXmpp::SceMode sceMode) { if (sceMode & QXmpp::ScePublic) { if (sceMode == QXmpp::ScePublic && element.tagName() == QStringLiteral("body")) { d->e2eeFallbackBody = element.text(); return true; } // XEP-0280: Message Carbons if (checkElement(element, QStringLiteral("private"), ns_carbons)) { d->privatemsg = true; return true; } // XEP-0334: Message Processing Hints if (element.namespaceURI() == ns_message_processing_hints && HINT_TYPES.contains(element.tagName())) { if (const auto index = HINT_TYPES.indexOf(element.tagName()); index >= 0) { addHint(Hint(1 << index)); } return true; } // XEP-0359: Unique and Stable Stanza IDs if (checkElement(element, QStringLiteral("stanza-id"), ns_sid)) { d->stanzaId = element.attribute(QStringLiteral("id")); d->stanzaIdBy = element.attribute(QStringLiteral("by")); return true; } if (checkElement(element, QStringLiteral("origin-id"), ns_sid)) { d->originId = element.attribute(QStringLiteral("id")); return true; } // XEP-0369: Mediated Information eXchange (MIX) if (checkElement(element, QStringLiteral("mix"), ns_mix)) { d->mixUserJid = element.firstChildElement(QStringLiteral("jid")).text(); d->mixUserNick = element.firstChildElement(QStringLiteral("nick")).text(); return true; } // XEP-0380: Explicit Message Encryption if (checkElement(element, QStringLiteral("encryption"), ns_eme)) { d->encryptionMethod = element.attribute(QStringLiteral("namespace")); d->encryptionName = element.attribute(QStringLiteral("name")); return true; } // XEP-0384: OMEMO Encryption if (QXmppOmemoElement::isOmemoElement(element)) { QXmppOmemoElement omemoElement; omemoElement.parse(element); d->omemoElement = omemoElement; return true; } // XEP-0428: Fallback Indication if (checkElement(element, QStringLiteral("fallback"), ns_fallback_indication)) { d->isFallback = true; return true; } } if (sceMode & QXmpp::SceSensitive) { if (element.tagName() == QStringLiteral("body")) { d->body = element.text(); return true; } if (element.tagName() == QStringLiteral("subject")) { d->subject = element.text(); return true; } if (element.tagName() == QStringLiteral("thread")) { d->thread = element.text(); d->parentThread = element.attribute(QStringLiteral("parent")); return true; } if (element.tagName() == QStringLiteral("x")) { if (element.namespaceURI() == ns_legacy_delayed_delivery) { // if XEP-0203 exists, XEP-0091 has no need to parse because XEP-0091 // is no more standard protocol) if (d->stamp.isNull()) { // XEP-0091: Legacy Delayed Delivery d->stamp = QDateTime::fromString( element.attribute(QStringLiteral("stamp")), QStringLiteral("yyyyMMddThh:mm:ss")); d->stamp.setTimeSpec(Qt::UTC); d->stampType = LegacyDelayedDelivery; } return true; } // XEP-0249: Direct MUC Invitations if (element.namespaceURI() == ns_conference) { d->mucInvitationJid = element.attribute(QStringLiteral("jid")); d->mucInvitationPassword = element.attribute(QStringLiteral("password")); d->mucInvitationReason = element.attribute(QStringLiteral("reason")); return true; } // XEP-0066: Out of Band Data if (element.namespaceURI() == ns_oob) { d->outOfBandUrl = element.firstChildElement(QStringLiteral("url")).text(); return true; } } // XEP-0071: XHTML-IM if (checkElement(element, QStringLiteral("html"), ns_xhtml_im)) { QDomElement bodyElement = element.firstChildElement(QStringLiteral("body")); if (!bodyElement.isNull() && bodyElement.namespaceURI() == ns_xhtml) { QTextStream stream(&d->xhtml, QIODevice::WriteOnly); bodyElement.save(stream, 0); d->xhtml = d->xhtml.mid(d->xhtml.indexOf('>') + 1); d->xhtml.replace( QStringLiteral(" xmlns=\"http://www.w3.org/1999/xhtml\""), QString()); d->xhtml.replace(QStringLiteral(""), QString()); d->xhtml = d->xhtml.trimmed(); } return true; } // XEP-0085: Chat State Notifications if (element.namespaceURI() == ns_chat_states) { int i = CHAT_STATES.indexOf(element.tagName()); if (i > 0) d->state = static_cast(i); return true; } // XEP-0184: Message Delivery Receipts if (checkElement(element, QStringLiteral("received"), ns_message_receipts)) { d->receiptId = element.attribute(QStringLiteral("id")); // compatibility with old-style XEP if (d->receiptId.isEmpty()) d->receiptId = id(); return true; } if (checkElement(element, QStringLiteral("request"), ns_message_receipts)) { d->receiptRequested = true; return true; } // XEP-0203: Delayed Delivery if (checkElement(element, QStringLiteral("delay"), ns_delayed_delivery)) { d->stamp = QXmppUtils::datetimeFromString( element.attribute(QStringLiteral("stamp"))); d->stampType = DelayedDelivery; return true; } // XEP-0224: Attention if (checkElement(element, QStringLiteral("attention"), ns_attention)) { d->attentionRequested = true; return true; } // XEP-0231: Bits of Binary if (QXmppBitsOfBinaryData::isBitsOfBinaryData(element)) { QXmppBitsOfBinaryData data; data.parseElementFromChild(element); d->bitsOfBinaryData << data; return true; } // XEP-0308: Last Message Correction if (checkElement(element, QStringLiteral("replace"), ns_message_correct)) { d->replaceId = element.attribute(QStringLiteral("id")); return true; } // XEP-0333: Chat Markers if (element.namespaceURI() == ns_chat_markers) { if (element.tagName() == QStringLiteral("markable")) { d->markable = true; } else { int marker = MARKER_TYPES.indexOf(element.tagName()); if (marker != -1) { d->marker = static_cast(marker); d->markedId = element.attribute(QStringLiteral("id")); d->markedThread = element.attribute(QStringLiteral("thread")); } } return true; } // XEP-0367: Message Attaching if (checkElement(element, QStringLiteral("attach-to"), ns_message_attaching)) { d->attachId = element.attribute(QStringLiteral("id")); return true; } // XEP-0382: Spoiler messages if (checkElement(element, QStringLiteral("spoiler"), ns_spoiler)) { d->isSpoiler = true; d->spoilerHint = element.text(); return true; } // XEP-0407: Mediated Information eXchange (MIX): Miscellaneous Capabilities if (checkElement(element, QStringLiteral("invitation"), ns_mix_misc)) { QXmppMixInvitation mixInvitation; mixInvitation.parse(element); d->mixInvitation = mixInvitation; return true; } // XEP-0434: Trust Messages (TM) if (QXmppTrustMessageElement::isTrustMessageElement(element)) { QXmppTrustMessageElement trustMessageElement; trustMessageElement.parse(element); d->trustMessageElement = trustMessageElement; return true; } } return false; } /// /// Serializes all additional child elements. /// /// \param writer The XML stream writer to output the XML /// \param sceMode The mode which decides which elements to output (only useful /// for encryption) /// \param baseNamespace Custom namespace added to basic XMPP-Core elements like /// <body/> (needed when encrypting elements outside of the stream). /// /// \since QXmpp 1.5 /// void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode sceMode, const QString &baseNamespace) const { if (sceMode & QXmpp::ScePublic) { if (sceMode == QXmpp::ScePublic && !d->e2eeFallbackBody.isEmpty()) { writer->writeTextElement(QStringLiteral("body"), d->e2eeFallbackBody); } // XEP-0280: Message Carbons if (d->privatemsg) { writer->writeStartElement(QStringLiteral("private")); writer->writeDefaultNamespace(ns_carbons); writer->writeEndElement(); } // XEP-0334: Message Processing Hints for (quint8 i = 0; i < HINT_TYPES.size(); i++) { if (hasHint(Hint(1 << i))) { writer->writeStartElement(HINT_TYPES.at(i)); writer->writeDefaultNamespace(ns_message_processing_hints); writer->writeEndElement(); } } // XEP-0359: Unique and Stable Stanza IDs if (!d->stanzaId.isNull()) { writer->writeStartElement(QStringLiteral("stanza-id")); writer->writeDefaultNamespace(ns_sid); writer->writeAttribute(QStringLiteral("id"), d->stanzaId); if (!d->stanzaIdBy.isNull()) writer->writeAttribute(QStringLiteral("by"), d->stanzaIdBy); writer->writeEndElement(); } if (!d->originId.isNull()) { writer->writeStartElement(QStringLiteral("origin-id")); writer->writeDefaultNamespace(ns_sid); writer->writeAttribute(QStringLiteral("id"), d->originId); writer->writeEndElement(); } // XEP-0369: Mediated Information eXchange (MIX) if (!d->mixUserJid.isEmpty() || !d->mixUserNick.isEmpty()) { writer->writeStartElement(QStringLiteral("mix")); writer->writeDefaultNamespace(ns_mix); helperToXmlAddTextElement(writer, QStringLiteral("jid"), d->mixUserJid); helperToXmlAddTextElement(writer, QStringLiteral("nick"), d->mixUserNick); writer->writeEndElement(); } // XEP-0380: Explicit Message Encryption if (!d->encryptionMethod.isEmpty()) { writer->writeStartElement(QStringLiteral("encryption")); writer->writeDefaultNamespace(ns_eme); writer->writeAttribute(QStringLiteral("namespace"), d->encryptionMethod); helperToXmlAddAttribute(writer, QStringLiteral("name"), d->encryptionName); writer->writeEndElement(); } // XEP-0384: OMEMO Encryption if (d->omemoElement) { d->omemoElement->toXml(writer); } // XEP-0428: Fallback Indication if (d->isFallback) { writer->writeStartElement(QStringLiteral("fallback")); writer->writeDefaultNamespace(ns_fallback_indication); writer->writeEndElement(); } } if (sceMode & QXmpp::SceSensitive) { const auto writeTextElement = [writer, &baseNamespace](const QString &tagName, const QString &text) { if (!text.isEmpty()) { writer->writeStartElement(tagName); if (!baseNamespace.isNull()) { writer->writeDefaultNamespace(baseNamespace); } writer->writeCharacters(text); writer->writeEndElement(); } }; // XMPP-Core writeTextElement(QStringLiteral("subject"), d->subject); writeTextElement(QStringLiteral("body"), d->body); if (!d->thread.isEmpty()) { writer->writeStartElement(QStringLiteral("thread")); if (!baseNamespace.isNull()) { writer->writeDefaultNamespace(baseNamespace); } helperToXmlAddAttribute(writer, QStringLiteral("parent"), d->parentThread); writer->writeCharacters(d->thread); writer->writeEndElement(); } // XEP-0066: Out of Band Data if (!d->outOfBandUrl.isEmpty()) { writer->writeStartElement(QStringLiteral("x")); writer->writeDefaultNamespace(ns_oob); writer->writeTextElement(QStringLiteral("url"), d->outOfBandUrl); writer->writeEndElement(); } // XEP-0071: XHTML-IM if (!d->xhtml.isEmpty()) { writer->writeStartElement(QStringLiteral("html")); writer->writeDefaultNamespace(ns_xhtml_im); writer->writeStartElement(QStringLiteral("body")); writer->writeDefaultNamespace(ns_xhtml); writer->writeCharacters(QString()); writer->device()->write(d->xhtml.toUtf8()); writer->writeEndElement(); writer->writeEndElement(); } // XEP-0085: Chat State Notifications if (d->state > None && d->state <= Paused) { writer->writeStartElement(CHAT_STATES.at(d->state)); writer->writeDefaultNamespace(ns_chat_states); writer->writeEndElement(); } // XEP-0091: Legacy Delayed Delivery | XEP-0203: Delayed Delivery if (d->stamp.isValid()) { QDateTime utcStamp = d->stamp.toUTC(); if (d->stampType == DelayedDelivery) { // XEP-0203: Delayed Delivery writer->writeStartElement(QStringLiteral("delay")); writer->writeDefaultNamespace(ns_delayed_delivery); helperToXmlAddAttribute(writer, QStringLiteral("stamp"), QXmppUtils::datetimeToString(utcStamp)); writer->writeEndElement(); } else { // XEP-0091: Legacy Delayed Delivery writer->writeStartElement(QStringLiteral("x")); writer->writeDefaultNamespace(ns_legacy_delayed_delivery); helperToXmlAddAttribute(writer, QStringLiteral("stamp"), utcStamp.toString(QStringLiteral("yyyyMMddThh:mm:ss"))); writer->writeEndElement(); } } // XEP-0184: Message Delivery Receipts // An ack message (message containing a "received" element) must not // include a receipt request ("request" element) in order to prevent // looping. if (!d->receiptId.isEmpty()) { writer->writeStartElement(QStringLiteral("received")); writer->writeDefaultNamespace(ns_message_receipts); writer->writeAttribute(QStringLiteral("id"), d->receiptId); writer->writeEndElement(); } else if (d->receiptRequested) { writer->writeStartElement(QStringLiteral("request")); writer->writeDefaultNamespace(ns_message_receipts); writer->writeEndElement(); } // XEP-0224: Attention if (d->attentionRequested) { writer->writeStartElement(QStringLiteral("attention")); writer->writeDefaultNamespace(ns_attention); writer->writeEndElement(); } // XEP-0249: Direct MUC Invitations if (!d->mucInvitationJid.isEmpty()) { writer->writeStartElement(QStringLiteral("x")); writer->writeDefaultNamespace(ns_conference); writer->writeAttribute(QStringLiteral("jid"), d->mucInvitationJid); if (!d->mucInvitationPassword.isEmpty()) writer->writeAttribute(QStringLiteral("password"), d->mucInvitationPassword); if (!d->mucInvitationReason.isEmpty()) writer->writeAttribute(QStringLiteral("reason"), d->mucInvitationReason); writer->writeEndElement(); } // XEP-0231: Bits of Binary for (const auto &data : std::as_const(d->bitsOfBinaryData)) data.toXmlElementFromChild(writer); // XEP-0308: Last Message Correction if (!d->replaceId.isEmpty()) { writer->writeStartElement(QStringLiteral("replace")); writer->writeDefaultNamespace(ns_message_correct); writer->writeAttribute(QStringLiteral("id"), d->replaceId); writer->writeEndElement(); } // XEP-0333: Chat Markers if (d->markable) { writer->writeStartElement(QStringLiteral("markable")); writer->writeDefaultNamespace(ns_chat_markers); writer->writeEndElement(); } if (d->marker != NoMarker) { writer->writeStartElement(MARKER_TYPES.at(d->marker)); writer->writeDefaultNamespace(ns_chat_markers); writer->writeAttribute(QStringLiteral("id"), d->markedId); if (!d->markedThread.isNull() && !d->markedThread.isEmpty()) { writer->writeAttribute(QStringLiteral("thread"), d->markedThread); } writer->writeEndElement(); } // XEP-0367: Message Attaching if (!d->attachId.isEmpty()) { writer->writeStartElement(QStringLiteral("attach-to")); writer->writeDefaultNamespace(ns_message_attaching); writer->writeAttribute(QStringLiteral("id"), d->attachId); writer->writeEndElement(); } // XEP-0382: Spoiler messages if (d->isSpoiler) { writer->writeStartElement(QStringLiteral("spoiler")); writer->writeDefaultNamespace(ns_spoiler); writer->writeCharacters(d->spoilerHint); writer->writeEndElement(); } // XEP-0407: Mediated Information eXchange (MIX): Miscellaneous Capabilities if (d->mixInvitation) { d->mixInvitation->toXml(writer); } // XEP-0434: Trust Messages (TM) if (d->trustMessageElement) { d->trustMessageElement->toXml(writer); } } }