// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <manjeetdahiya@gmail.com>
// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
// SPDX-FileCopyrightText: 2015 Georg Rudoy <0xd34df00d@gmail.com>
// SPDX-FileCopyrightText: 2019 Linus Jahn <lnj@kaidan.im>
// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppStanza.h"

#include "QXmppConstants_p.h"
#include "QXmppE2eeMetadata.h"
#include "QXmppStanza_p.h"
#include "QXmppUtils.h"
#include "QXmppUtils_p.h"

#include "Algorithms.h"
#include "StringLiterals.h"
#include "XmlWriter.h"

#include <QDateTime>
#include <QDomElement>
#include <QXmlStreamWriter>

using namespace QXmpp;
using namespace QXmpp::Private;

template<>
struct Enums::Data<JingleErrorCondition> {
    using enum JingleErrorCondition;
    static constexpr auto Values = makeValues<JingleErrorCondition>({
        { OutOfOrder, u"out-of-order" },
        { TieBreak, u"tie-break" },
        { UnknownSession, u"unknown-session" },
        { UnsupportedInfo, u"unsupported-info" },
    });
};

class QXmppExtendedAddressPrivate : public QSharedData
{
public:
    bool delivered;
    QString description;
    QString jid;
    QString type;
};

/*! Constructs an empty extended address. */
QXmppExtendedAddress::QXmppExtendedAddress()
    : d(new QXmppExtendedAddressPrivate())
{
    d->delivered = false;
}

/*! Default copy-constructur */
QXmppExtendedAddress::QXmppExtendedAddress(const QXmppExtendedAddress &other) = default;
/*! Default move-constructur */
QXmppExtendedAddress::QXmppExtendedAddress(QXmppExtendedAddress &&) = default;
QXmppExtendedAddress::~QXmppExtendedAddress() = default;
/*! Default assignment operator */
QXmppExtendedAddress &QXmppExtendedAddress::operator=(const QXmppExtendedAddress &other) = default;
/*! Default assignment operator */
QXmppExtendedAddress &QXmppExtendedAddress::operator=(QXmppExtendedAddress &&) = default;

/*! Returns the human-readable description of the address. */
QString QXmppExtendedAddress::description() const
{
    return d->description;
}

/*! Sets the human-readable \a description of the address. */
void QXmppExtendedAddress::setDescription(const QString &description)
{
    d->description = description;
}

/*! Returns the JID of the address. */
QString QXmppExtendedAddress::jid() const
{
    return d->jid;
}

/*!
    Sets the JID of the address.

    \a jid.
*/
void QXmppExtendedAddress::setJid(const QString &jid)
{
    d->jid = jid;
}

/*! Returns the type of the address. */
QString QXmppExtendedAddress::type() const
{
    return d->type;
}

/*! Sets the \a type of the address. */
void QXmppExtendedAddress::setType(const QString &type)
{
    d->type = type;
}

/*! Returns whether the stanza has been delivered to this address. */
bool QXmppExtendedAddress::isDelivered() const
{
    return d->delivered;
}

/*! Sets whether the stanza has been \a delivered to this address. */
void QXmppExtendedAddress::setDelivered(bool delivered)
{
    d->delivered = delivered;
}

/*!
    Returns true if this address is valid. The extended address is considered
    to be valid if at least type and JID fields are non-empty.
*/
bool QXmppExtendedAddress::isValid() const
{
    return !d->type.isEmpty() && !d->jid.isEmpty();
}

void QXmppExtendedAddress::parse(const QDomElement &element)
{
    d->delivered = element.attribute(u"delivered"_s) == u"true";
    d->description = element.attribute(u"desc"_s);
    d->jid = element.attribute(u"jid"_s);
    d->type = element.attribute(u"type"_s);
}

void QXmppExtendedAddress::toXml(QXmlStreamWriter *xmlWriter) const
{
    XmlWriter(xmlWriter).write(Element {
        u"address",
        OptionalAttribute { u"delivered", DefaultedBool { d->delivered, false } },
        OptionalAttribute { u"desc", d->description },
        Attribute { u"jid", d->jid },
        Attribute { u"type", d->type },
    });
}

class QXmppStanzaErrorPrivate : public QSharedData
{
public:
    int code = 0;
    QXmppStanza::Error::Type type = QXmppStanza::Error::NoType;
    QXmppStanza::Error::Condition condition = QXmppStanza::Error::NoCondition;
    QString text;
    QString by;
    QString redirectionUri;

    // XEP-0166: Jingle
    std::optional<JingleErrorCondition> jingleErrorCondition;

    // XEP-0363: HTTP File Upload
    bool fileTooLarge = false;
    qint64 maxFileSize;
    QDateTime retryDate;
};

/*! Default constructor */
QXmppStanza::Error::Error()
    : d(new QXmppStanzaErrorPrivate)
{
}

/*! Copy constructor */
QXmppStanza::Error::Error(const QXmppStanza::Error &) = default;
/*! Move constructor */
QXmppStanza::Error::Error(QXmppStanza::Error &&) = default;

/*! Initializes an error with a type, condition and text. */
QXmppStanza::Error::Error(Type type, Condition cond, const QString &text)
    : d(new QXmppStanzaErrorPrivate)
{
    d->type = type;
    d->condition = cond;
    d->text = text;
}

/*! Initializes an error with a type, condition and text (all from strings). */
QXmppStanza::Error::Error(const QString &type, const QString &cond,
                          const QString &text)
    : d(new QXmppStanzaErrorPrivate)
{
    d->text = text;
    d->type = Enums::fromString<Type>(type).value_or(NoType);
    d->condition = Enums::fromString<Condition>(cond).value_or(NoCondition);
}

QXmppStanza::Error::Error(QSharedDataPointer<QXmppStanzaErrorPrivate> d)
    : d(std::move(d))
{
}

/*! Default destructor */
QXmppStanza::Error::~Error() = default;
/*! Copy operator */
QXmppStanza::Error &QXmppStanza::Error::operator=(const QXmppStanza::Error &) = default;
/*! Move operator */
QXmppStanza::Error &QXmppStanza::Error::operator=(QXmppStanza::Error &&) = default;

/*! Returns the human-readable description of the error. */
QString QXmppStanza::Error::text() const
{
    return d->text;
}

/*!
    Sets the description of the error.

    \a text.
*/
void QXmppStanza::Error::setText(const QString &text)
{
    d->text = text;
}

/*! Returns the error code. */
int QXmppStanza::Error::code() const
{
    return d->code;
}

/*! Sets the error \a code. */
void QXmppStanza::Error::setCode(int code)
{
    d->code = code;
}

/*!
    Returns the error condition.

    The conditions QXmppStanza::Error::Gone and QXmppStanza::Error::Redirect
    can be used in combination with redirectUri().
*/
QXmppStanza::Error::Condition QXmppStanza::Error::condition() const
{
    return d->condition;
}

/*!
    Sets the error condition.

    The conditions QXmppStanza::Error::Gone and QXmppStanza::Error::Redirect
    can be used in combination with setRedirectUri().

    \a cond.
*/
void QXmppStanza::Error::setCondition(Condition cond)
{
    d->condition = cond;
}

/*! Returns the type of the error. */
QXmppStanza::Error::Type QXmppStanza::Error::type() const
{
    return d->type;
}

/*!
    Returns the optional JID of the creator of the error.

    This is useful to ditinguish between errors generated by the local server
    and by the remote server for example. However, the value is optional.

    \since QXmpp 1.3
*/
QString QXmppStanza::Error::by() const
{
    return d->by;
}

/*!
    Sets the optional JID of the creator of the error.

    This is useful to ditinguish between errors generated by the local server
    and by the remote server for example. However, the value is optional.

    \since QXmpp 1.3

    \a by.
*/
void QXmppStanza::Error::setBy(const QString &by)
{
    d->by = by;
}

/*! Sets the \a type of the error. */
void QXmppStanza::Error::setType(QXmppStanza::Error::Type type)
{
    d->type = type;
}

/*!
    Returns the optionally included redirection URI for the error conditions
    QXmppStanza::Error::Gone and QXmppStanza::Error::Redirect.

    \sa setRedirectionUri()

    \since QXmpp 1.3
*/
QString QXmppStanza::Error::redirectionUri() const
{
    return d->redirectionUri;
}

/*!
    Sets the optional redirection URI for the error conditions
    QXmppStanza::Error::Gone and QXmppStanza::Error::Redirect.

    \sa redirectionUri()

    \since QXmpp 1.3

    \a redirectionUri.
*/
void QXmppStanza::Error::setRedirectionUri(const QString &redirectionUri)
{
    d->redirectionUri = redirectionUri;
}

/*!
    Returns an additional \xep{0166}{Jingle}-specific error condition.

    \since QXmpp 1.14
*/
std::optional<QXmpp::JingleErrorCondition> QXmppStanza::Error::jingleErrorCondition() const
{
    return d->jingleErrorCondition;
}

/*!
    Sets an additional \xep{0166}{Jingle}-specific error condition.

    \since QXmpp 1.14

    \a condition.
*/
void QXmppStanza::Error::setJingleErrorCondition(std::optional<QXmpp::JingleErrorCondition> condition)
{
    d->jingleErrorCondition = condition;
}

/*!
    Returns true, if an HTTP File Upload failed, because the file was too
    large.

    \since QXmpp 1.1
*/
bool QXmppStanza::Error::fileTooLarge() const
{
    return d->fileTooLarge;
}

/*!
    Sets whether the requested file for HTTP File Upload was too large.

    You should also set maxFileSize in this case.

    \since QXmpp 1.1

    \a fileTooLarge.
*/
void QXmppStanza::Error::setFileTooLarge(bool fileTooLarge)
{
    d->fileTooLarge = fileTooLarge;
}

/*!
    Returns the maximum file size allowed for uploading via. HTTP File Upload.

    \since QXmpp 1.1
*/
qint64 QXmppStanza::Error::maxFileSize() const
{
    return d->maxFileSize;
}

/*!
    Sets the maximum file size allowed for uploading via. HTTP File Upload.

    This sets fileTooLarge to true.

    \since QXmpp 1.1

    \a maxFileSize.
*/
void QXmppStanza::Error::setMaxFileSize(qint64 maxFileSize)
{
    setFileTooLarge(true);
    d->maxFileSize = maxFileSize;
}

/*!
    Returns when to retry the upload request via. HTTP File Upload.

    \since QXmpp 1.1
*/
QDateTime QXmppStanza::Error::retryDate() const
{
    return d->retryDate;
}

/*!
    Sets the datetime when the client can retry to request the upload slot.

    \a retryDate.
*/
void QXmppStanza::Error::setRetryDate(const QDateTime &retryDate)
{
    d->retryDate = retryDate;
}

void QXmppStanza::Error::parse(const QDomElement &errorElement)
{
    d->code = errorElement.attribute(u"code"_s).toInt();
    d->type = Enums::fromString<Type>(errorElement.attribute(u"type"_s)).value_or(NoType);
    d->by = errorElement.attribute(u"by"_s);

    for (const auto &element : iterChildElements(errorElement)) {
        if (element.namespaceURI() == ns_stanza) {
            if (element.tagName() == u"text") {
                d->text = element.text();
            } else {
                d->condition = Enums::fromString<Condition>(element.tagName()).value_or(NoCondition);

                // redirection URI
                if (d->condition == Gone || d->condition == Redirect) {
                    d->redirectionUri = element.text();

                    // .text() returns empty string if nothing was set
                    if (d->redirectionUri.isEmpty()) {
                        d->redirectionUri.clear();
                    }
                }
            }
        } else if (element.namespaceURI() == ns_http_upload) {
            // XEP-0363: HTTP File Upload
            // file is too large
            if (element.tagName() == u"file-too-large") {
                d->fileTooLarge = true;
                d->maxFileSize = element.firstChildElement(u"max-file-size"_s)
                                     .text()
                                     .toLongLong();
                // retry later
            } else if (element.tagName() == u"retry") {
                d->retryDate = QXmppUtils::datetimeFromString(
                    element.attribute(u"stamp"_s));
            }
        }
    }

    d->jingleErrorCondition = Enums::fromString<JingleErrorCondition>(
        firstChildElement(errorElement, {}, ns_jingle_errors).tagName());
}

void QXmppStanza::Error::toXml(QXmlStreamWriter *writer) const
{
    if (d->condition == NoCondition && d->type == NoType) {
        return;
    }

    auto optionalInt = [](int n) {
        return n > 0 ? std::make_optional(n) : std::nullopt;
    };

    XmlWriter(writer).write(Element {
        u"error",
        OptionalAttribute { u"by", d->by },
        OptionalAttribute { u"type", d->type },
        OptionalAttribute { u"code", optionalInt(d->code) },
        OptionalContent {
            d->condition != NoCondition,
            Element {
                Tag { d->condition, ns_stanza },
                OptionalContent {
                    !d->redirectionUri.isEmpty() && (d->condition == Gone || d->condition == Redirect),
                    Characters { d->redirectionUri },
                },
            },
        },
        OptionalContent {
            !d->text.isEmpty(),
            Element {
                { u"text", ns_stanza },
                Attribute { u"xml:lang", u"en"_s },
                Characters { d->text },
            },
        },
        // XEP-0166: Jingle
        OptionalEnumElement { d->jingleErrorCondition, ns_jingle_errors },
        // XEP-0363: HTTP File Upload
        OptionalContent {
            d->fileTooLarge,
            Element {
                { u"file-too-large", ns_http_upload },
                TextElement { u"max-file-size", d->maxFileSize },
            },
        },
        OptionalContent {
            d->retryDate.isValid(),
            Element {
                { u"retry", ns_http_upload },
                Attribute { u"stamp", d->retryDate },
            },
        },
    });
}

/*!
    \class QXmppE2eeMetadata
    \inmodule QXmpp

    \brief The QXmppE2eeMetadata class contains data used for end-to-end
    encryption purposes.

    \since QXmpp 1.5
*/

class QXmppE2eeMetadataPrivate : public QSharedData
{
public:
    QXmpp::EncryptionMethod encryption;
    QByteArray senderKey;

    // XEP-0420: Stanza Content Encryption
    QDateTime sceTimestamp;
};

/*! Constructs a class for end-to-end encryption metadata. */
QXmppE2eeMetadata::QXmppE2eeMetadata()
    : d(new QXmppE2eeMetadataPrivate)
{
}

QXmppE2eeMetadata::QXmppE2eeMetadata(QSharedDataPointer<QXmppE2eeMetadataPrivate> d)
    : d(d)
{
}

/*! Copy-constructor. */
QXmppE2eeMetadata::QXmppE2eeMetadata(const QXmppE2eeMetadata &other) = default;
/*! Move-constructor. */
QXmppE2eeMetadata::QXmppE2eeMetadata(QXmppE2eeMetadata &&) = default;
QXmppE2eeMetadata::~QXmppE2eeMetadata() = default;
/*! Assignment operator. */
QXmppE2eeMetadata &QXmppE2eeMetadata::operator=(const QXmppE2eeMetadata &other) = default;
/*! Assignment move-operator. */
QXmppE2eeMetadata &QXmppE2eeMetadata::operator=(QXmppE2eeMetadata &&) = default;

/*!
    Returns the used encryption protocol.
*/
QXmpp::EncryptionMethod QXmppE2eeMetadata::encryption() const
{
    return d->encryption;
}

/*!
    Sets the used \a encryption protocol.
*/
void QXmppE2eeMetadata::setEncryption(QXmpp::EncryptionMethod encryption)
{
    d->encryption = encryption;
}

/*!
    Returns the ID of this stanza's sender's public long-term key.

    The sender key ID is not part of a transmitted stanza and thus not de- /
    serialized.
    Instead, the key ID is set by an encryption protocol such as
    \xep{0384}{OMEMO Encryption} during decryption.
    It can be used by trust management protocols such as
    \xep{0450}{Automatic Trust Management (ATM)}.

    \since QXmpp 1.5
*/
QByteArray QXmppE2eeMetadata::senderKey() const
{
    return d->senderKey;
}

/*!
    Sets the ID of this stanza's sender's public long-term key to \a keyId.

    The sender key ID is not part of a transmitted stanza and thus not de- /
    serialized.
    Instead, it is set by an encryption protocol such as
    \xep{0384}{OMEMO Encryption} during decryption.
    It can be used by trust management protocols such as
    \xep{0450}{Automatic Trust Management (ATM)}.

    \since QXmpp 1.5
*/
void QXmppE2eeMetadata::setSenderKey(const QByteArray &keyId)
{
    d->senderKey = keyId;
}

/*!
    Returns the timestamp affix element's content as defined by
    \xep{0420}{Stanza Content Encryption} (SCE).

    The SCE timestamp is part of an encrypted stanza's SCE envelope,
    not an unencrypted direct child of a transmitted stanza and thus not de- /
    serialized by it.
    Instead, it is set by an encryption protocol such as
    \xep{0384}{OMEMO Encryption} after decryption.
    It can be used by trust management protocols such as
    \xep{0450}{Automatic Trust Management (ATM)}.

    \since QXmpp 1.5
*/
QDateTime QXmppE2eeMetadata::sceTimestamp() const
{
    return d->sceTimestamp;
}

/*!
    Sets the timestamp affix element's content as defined by
    \xep{0420}{Stanza Content Encryption} (SCE).

    The SCE timestamp is part of an encrypted stanza's SCE envelope,
    not an unencrypted direct child of a transmitted stanza and thus not de- /
    serialized by it.
    Instead, it is set by an encryption protocol such as
    \xep{0384}{OMEMO Encryption} after decryption.
    It can be used by trust management protocols such as
    \xep{0450}{Automatic Trust Management (ATM)}.

    \since QXmpp 1.5

    \a timestamp.
*/
void QXmppE2eeMetadata::setSceTimestamp(const QDateTime &timestamp)
{
    d->sceTimestamp = timestamp;
}

class QXmppStanzaPrivate : public QSharedData
{
public:
    QString to;
    QString from;
    QString id;
    QString lang;
    QSharedDataPointer<QXmppStanzaErrorPrivate> error;
    QXmppElementList extensions;
    QList<QXmppExtendedAddress> extendedAddresses;
    QSharedDataPointer<QXmppE2eeMetadataPrivate> e2eeMetadata;
};

/*!
    Constructs a QXmppStanza with the specified sender \a from and recipient \a to.
*/
QXmppStanza::QXmppStanza(const QString &from, const QString &to)
    : d(new QXmppStanzaPrivate)
{
    d->to = to;
    d->from = from;
}

/*! Constructs a copy of \a other. */
QXmppStanza::QXmppStanza(const QXmppStanza &other) = default;
/*! Move constructor. */
QXmppStanza::QXmppStanza(QXmppStanza &&) = default;
/*! Destroys a QXmppStanza. */
QXmppStanza::~QXmppStanza() = default;
/*! Assigns \a other to this stanza. */
QXmppStanza &QXmppStanza::operator=(const QXmppStanza &other) = default;
/*! Move-assignment operator. */
QXmppStanza &QXmppStanza::operator=(QXmppStanza &&) = default;

/*! Returns the stanza's recipient JID. */
QString QXmppStanza::to() const
{
    return d->to;
}

/*!
    Sets the stanza's recipient JID to \a to.
*/
void QXmppStanza::setTo(const QString &to)
{
    d->to = to;
}

/*! Returns the stanza's sender JID. */
QString QXmppStanza::from() const
{
    return d->from;
}

/*!
    Sets the stanza's sender JID to \a from.
*/
void QXmppStanza::setFrom(const QString &from)
{
    d->from = from;
}

/*! Returns the stanza's identifier. */
QString QXmppStanza::id() const
{
    return d->id;
}

/*!
    Sets the stanza's identifier to \a id.
*/
void QXmppStanza::setId(const QString &id)
{
    d->id = id;
}

/*! Returns the stanza's language. */
QString QXmppStanza::lang() const
{
    return d->lang;
}

/*!
    Sets the stanza's language to \a lang.
*/
void QXmppStanza::setLang(const QString &lang)
{
    d->lang = lang;
}

/*!
    Returns the stanza's error.

    If the stanza has no error a default constructed QXmppStanza::Error is returned.
*/
QXmppStanza::Error QXmppStanza::error() const
{
    return d->error ? Error { d->error } : Error();
}

/*!
    Returns the stanza's error.

    \since QXmpp 1.5
*/
std::optional<QXmppStanza::Error> QXmppStanza::errorOptional() const
{
    if (d->error) {
        return Error { d->error };
    }
    return {};
}

/*!
    Sets the stanza's \a error.
*/
void QXmppStanza::setError(const QXmppStanza::Error &error)
{
    d->error = error.d;
}

/*!
    Sets the stanza's error.

    If you set an empty optional, this will remove the error.

    \since QXmpp 1.5
*/
void QXmppStanza::setError(const std::optional<Error> &error)
{
    if (error) {
        d->error = error->d;
    } else {
        d->error = nullptr;
    }
}

/*!
    Returns the stanza's "extensions".

    Extensions are XML elements which are not handled internally by QXmpp.
*/
QXmppElementList QXmppStanza::extensions() const
{
    return d->extensions;
}

/*!
    Sets the stanza's \a extensions.
*/
void QXmppStanza::setExtensions(const QXmppElementList &extensions)
{
    d->extensions = extensions;
}

/*!
    Returns the stanza's extended addresses as defined by \xep{0033}{Extended
    Stanza Addressing}.
*/
QList<QXmppExtendedAddress> QXmppStanza::extendedAddresses() const
{
    return d->extendedAddresses;
}

/*!
    Sets the stanza's extended addresses as defined by \xep{0033}{Extended
    Stanza Addressing}.

    \a addresses.
*/
void QXmppStanza::setExtendedAddresses(const QList<QXmppExtendedAddress> &addresses)
{
    d->extendedAddresses = addresses;
}

/*!
    Returns additional data for end-to-end encryption purposes.

    \since QXmpp 1.5
*/
std::optional<QXmppE2eeMetadata> QXmppStanza::e2eeMetadata() const
{
    if (d->e2eeMetadata) {
        return QXmppE2eeMetadata(d->e2eeMetadata);
    }
    return {};
}

/*!
    Sets additional data for end-to-end encryption purposes.

    \since QXmpp 1.5

    \a e2eeMetadata.
*/
void QXmppStanza::setE2eeMetadata(const std::optional<QXmppE2eeMetadata> &e2eeMetadata)
{
    if (e2eeMetadata) {
        d->e2eeMetadata = e2eeMetadata->d;
    } else {
        d->e2eeMetadata = nullptr;
    }
}

void QXmppStanza::generateAndSetNextId()
{
    d->id = generateSequentialStanzaId();
}

void QXmppStanza::parse(const QDomElement &element)
{
    d->from = element.attribute(u"from"_s);
    d->to = element.attribute(u"to"_s);
    d->id = element.attribute(u"id"_s);
    d->lang = element.attribute(u"lang"_s);

    auto error = parseOptionalChildElement<Error>(element, u"error", element.namespaceURI());
    d->error = error ? error->d : decltype(d->error) {};

    d->extendedAddresses = parseChildElements<QList<QXmppExtendedAddress>>(firstChildElement(element, u"addresses", ns_extended_addressing));
    removeIf(d->extendedAddresses, [](const auto &address) { return !address.isValid(); });
}

void QXmppStanza::extensionsToXml(QXmlStreamWriter *xmlWriter, QXmpp::SceMode sceMode) const
{
    XmlWriter w(xmlWriter);

    // XEP-0033: Extended Stanza Addressing
    if (sceMode & QXmpp::ScePublic && !d->extendedAddresses.isEmpty()) {
        w.write(Element { { u"addresses", ns_extended_addressing }, d->extendedAddresses });
    }

    // other extensions
    w.write(d->extensions);
}
