// SPDX-FileCopyrightText: 2009 Manjeet Dahiya <manjeetdahiya@gmail.com>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppConfiguration.h"

#include "QXmppConstants_p.h"
#include "QXmppCredentials.h"
#include "QXmppSasl2UserAgent.h"
#include "QXmppSasl_p.h"
#include "QXmppUtils.h"
#include "QXmppUtils_p.h"

#include "StringLiterals.h"

#include <QCoreApplication>
#include <QLocale>
#include <QNetworkProxy>
#include <QSslSocket>

using namespace QXmpp::Private;

struct QXmppCredentialsPrivate : QSharedData, Credentials { };

/*!
    \class QXmppCredentials
    \inmodule QXmpp

    \brief Stores different kinds of credentials used for authentication.

    QXmppCredentials can be serialized to XML and parsed from XML again. This can be useful to
    store credentials permanently without needing to handle all the details of the different
    authentication methods. QXmpp can for example request and use \xep{0484}{Fast Authentication
    Streamlining Tokens} tokens and might support other mechanisms in the future.
    The XML format is QXmpp specific and is not specified.

    The XML output currently may contain:
    \list
        \li an HT token for \xep{0484}{Fast Authentication Streamlining Tokens}
    \endlist

    \since QXmpp 1.8
*/

/*! Default constructor. */
QXmppCredentials::QXmppCredentials()
    : d(new QXmppCredentialsPrivate)
{
}

QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppCredentials)

/*!
    Tries to parse XML-serialized credentials.

    \a r.
*/
std::optional<QXmppCredentials> QXmppCredentials::fromXml(QXmlStreamReader &r)
{
    if (!r.isStartElement() || r.name() != u"credentials" || r.namespaceUri() != ns_qxmpp_credentials) {
        return {};
    }

    QXmppCredentials credentials;
    while (r.readNextStartElement()) {
        if (r.name() == u"ht-token") {
            if (auto htToken = HtToken::fromXml(r)) {
                credentials.d->htToken = std::move(*htToken);
            }
        }
    }
    return credentials;
}

/*!
    Serializes the credentials to XML.

    \a writer.
*/
void QXmppCredentials::toXml(QXmlStreamWriter &writer) const
{
    XmlWriter(&writer).write(Element {
        { u"credentials", ns_qxmpp_credentials },
        d->htToken,
    });
}

bool QXmppCredentials::operator==(const QXmppCredentials &other) const
{
    return d->htToken == other.d->htToken;
}

class QXmppConfigurationPrivate : public QSharedData
{
public:
    QString host;
    int port = XMPP_DEFAULT_PORT;
    QString user;
    QString domain;
    QString resource = u"QXmpp"_s;
    QString resourcePrefix;
    QXmppCredentials credentials;

    bool autoAcceptSubscriptions = false;
    bool sendIntialPresence = true;
    bool sendRosterRequest = true;
    // interval in seconds, if zero won't ping
    int keepAliveInterval = 60;
    // interval in seconds, if zero won't timeout
    int keepAliveTimeout = 20;
    // will keep reconnecting if disconnected, default is true
    bool autoReconnectionEnabled = true;
    // which authentication systems to use (if any)
    bool useSasl2Authentication = true;
    bool useFastTokenAuthentication = true;
    bool useSASLAuthentication = true;
    bool useNonSASLAuthentication = true;
    bool ignoreSslErrors = false;

    QXmppConfiguration::StreamSecurityMode streamSecurityMode = QXmppConfiguration::TLSEnabled;
    QXmppConfiguration::NonSASLAuthMechanism nonSASLAuthMechanism = QXmppConfiguration::NonSASLDigest;
    QString saslAuthMechanism;
    QList<QString> disabledSaslMechanisms = { u"PLAIN"_s };
    std::optional<QXmppSasl2UserAgent> sasl2UserAgent;

    QString locale;

    QNetworkProxy networkProxy;

    QList<QSslCertificate> caCertificates;
};

/*! Creates a QXmppConfiguration object. */
QXmppConfiguration::QXmppConfiguration()
    : d(new QXmppConfigurationPrivate)
{
}

/*! Creates a copy of \a other. */
QXmppConfiguration::QXmppConfiguration(const QXmppConfiguration &other) = default;

QXmppConfiguration::~QXmppConfiguration() = default;

/*! Assigns \a other to this QXmppConfiguration. */
QXmppConfiguration &QXmppConfiguration::operator=(const QXmppConfiguration &other) = default;

/*!
    Sets the host name.

    \a host is the host name of the XMPP server where connection has to be made
    (e.g. "jabber.org" and "talk.google.com"). It can also be an IP address in
    the form of a string (e.g. "192.168.1.25").
*/
void QXmppConfiguration::setHost(const QString &host)
{
    d->host = host;
}

/*!
    Sets the domain name to \a domain (e.g. "gmail.com" and "jabber.org").

    \note host name and domain name can be different for google
    domain name is gmail.com and host name is talk.google.com
*/
void QXmppConfiguration::setDomain(const QString &domain)
{
    d->domain = domain;
}

/*!
    Sets the \a port number at which the XMPP server is listening. The default
    value is 5222.
*/
void QXmppConfiguration::setPort(int port)
{
    d->port = port;
}

/*!
    Sets the username.

    \a user is the username of the account at the specified XMPP server. It should
    be the name without the domain name. E.g. "qxmpp.test1" and not
    "qxmpp.test1@gmail.com".
*/
void QXmppConfiguration::setUser(const QString &user)
{
    d->user = user;
}

/*!
    Sets the \a password for the specified username.
*/
void QXmppConfiguration::setPassword(const QString &password)
{
    credentialData().password = password;
}

/*!
    Sets the resource identifier.

    Multiple resources (e.g., devices or locations) may connect simultaneously
    to a server on behalf of each authorized client, with each resource
    differentiated by the resource identifier of an XMPP address
    (e.g. node\@domain/home vs. node\@domain/work)

    The default value is "QXmpp".

    \a resource is the resource identifier of the client in connection.
*/
void QXmppConfiguration::setResource(const QString &resource)
{
    d->resource = resource;
}

/*!
    Returns the resource prefix ('tag' for this client) used when \xep{0386}{Bind 2} is available.

    \since QXmpp 1.8
*/
QString QXmppConfiguration::resourcePrefix() const
{
    return d->resourcePrefix;
}

/*!
    Sets the resource prefix ('tag' for this client) used when \xep{0386}{Bind 2} is available.

    \since QXmpp 1.8

    \a resourcePrefix.
*/
void QXmppConfiguration::setResourcePrefix(const QString &resourcePrefix)
{
    d->resourcePrefix = resourcePrefix;
}

/*!
    Sets the JID. If a full JID (i.e. one with a resource) is given, calling
    this method will update the username, domain and resource. Otherwise, only
    the username and the domain will be updated.

    \a jid.
*/
void QXmppConfiguration::setJid(const QString &jid)
{
    d->user = QXmppUtils::jidToUser(jid);
    d->domain = QXmppUtils::jidToDomain(jid);
    const QString resource = QXmppUtils::jidToResource(jid);
    if (!resource.isEmpty()) {
        d->resource = resource;
    }
}

/*! Returns the custom hostname to connect to. */
QString QXmppConfiguration::host() const
{
    return d->host;
}

/*! Returns the domain part of the JID. */
QString QXmppConfiguration::domain() const
{
    return d->domain;
}

/*! Returns the port number. */
int QXmppConfiguration::port() const
{
    return d->port;
}

/*!
    Returns the port number as quint16.

    \since QXmpp 1.8
*/
quint16 QXmppConfiguration::port16() const
{
    if (d->port >= 0 && d->port <= std::numeric_limits<quint16>::max()) {
        return d->port;
    }
    return 0;
}

/*! Returns the localpart of the JID. */
QString QXmppConfiguration::user() const
{
    return d->user;
}

/*! Returns the password. */
QString QXmppConfiguration::password() const
{
    return credentialData().password;
}

/*! Returns the resource identifier. */
QString QXmppConfiguration::resource() const
{
    return d->resource;
}

/*!
    Returns the Jabber-ID (JID)
    (e.g. "qxmpp.test1@gmail.com/resource" or qxmpptest@jabber.org/QXmpp156).
*/
QString QXmppConfiguration::jid() const
{
    if (d->user.isEmpty()) {
        return d->domain;
    } else {
        return jidBare() + u'/' + d->resource;
    }
}

/*!
    Returns the bare jabber id (jid), without the resource identifier
    (e.g. "qxmpp.test1@gmail.com" or qxmpptest@jabber.org).
*/
QString QXmppConfiguration::jidBare() const
{
    if (d->user.isEmpty()) {
        return d->domain;
    } else {
        return d->user + u'@' + d->domain;
    }
}

/*!
    Returns the credentials of this configuration.

    \since QXmpp 1.8
*/
QXmppCredentials QXmppConfiguration::credentials() const
{
    return d->credentials;
}

/*!
    Sets the credentials for this configuration.

    \since QXmpp 1.8

    \a credentials.
*/
void QXmppConfiguration::setCredentials(const QXmppCredentials &credentials)
{
    d->credentials = credentials;
}

/*! Returns the access token used for X-FACEBOOK-PLATFORM authentication. */
QString QXmppConfiguration::facebookAccessToken() const
{
    return credentialData().facebookAccessToken;
}

/*!
    Sets the access token used for X-FACEBOOK-PLATFORM authentication.

    This token is returned by Facebook at the end of the OAuth authentication
    process.

    \a accessToken.
*/
void QXmppConfiguration::setFacebookAccessToken(const QString &accessToken)
{
    credentialData().facebookAccessToken = accessToken;
}

/*! Returns the application ID used for X-FACEBOOK-PLATFORM authentication. */
QString QXmppConfiguration::facebookAppId() const
{
    return credentialData().facebookAppId;
}

/*!
    Sets the application ID used for X-FACEBOOK-PLATFORM authentication.

    \a appId.
*/
void QXmppConfiguration::setFacebookAppId(const QString &appId)
{
    credentialData().facebookAppId = appId;
}

/*! Returns the access token used for X-OAUTH2 authentication. */
QString QXmppConfiguration::googleAccessToken() const
{
    return credentialData().googleAccessToken;
}

/*!
    Sets the access token used for X-OAUTH2 authentication.

    This token is returned by Google at the end of the OAuth authentication
    process.

    \a accessToken.
*/
void QXmppConfiguration::setGoogleAccessToken(const QString &accessToken)
{
    credentialData().googleAccessToken = accessToken;
}

/*! Returns the access token used for X-MESSENGER-OAUTH2 authentication. */
QString QXmppConfiguration::windowsLiveAccessToken() const
{
    return credentialData().windowsLiveAccessToken;
}

/*!
    Sets the access token used for X-MESSENGER-OAUTH2 authentication.

    This token is returned by Windows Live at the end of the OAuth authentication
    process.

    \a accessToken.
*/
void QXmppConfiguration::setWindowsLiveAccessToken(const QString &accessToken)
{
    credentialData().windowsLiveAccessToken = accessToken;
}

/*!
    Returns the auto-accept-subscriptions-request configuration: true means that
    auto-accept-subscriptions-request is enabled, else disabled for false.
*/
bool QXmppConfiguration::autoAcceptSubscriptions() const
{
    return d->autoAcceptSubscriptions;
}

/*!
    Sets the auto-accept-subscriptions-request configuration. \a value true means that
    auto-accept-subscriptions-request is enabled, else disabled for false.
*/
void QXmppConfiguration::setAutoAcceptSubscriptions(bool value)
{
    d->autoAcceptSubscriptions = value;
}

/*!
    Returns the auto-reconnect-on-disconnection-on-error configuration: true means that
    auto-reconnect is enabled, else disabled for false.
*/
bool QXmppConfiguration::autoReconnectionEnabled() const
{
    return d->autoReconnectionEnabled;
}

/*!
    Sets the auto-reconnect-on-disconnection-on-error configuration. \a value true means that
    auto-reconnect is enabled, else disabled for false.
*/
void QXmppConfiguration::setAutoReconnectionEnabled(bool value)
{
    d->autoReconnectionEnabled = value;
}

/*!
    Returns whether SASL 2 (\xep{0388}{Extensible SASL Profile}) authentication is used if
    available.
*/
bool QXmppConfiguration::useSasl2Authentication() const
{
    return d->useSasl2Authentication;
}

/*!
    Sets whether to use SASL 2 (\xep{0388}{Extensible SASL Profile}) authentication if available.

    \since QXmpp 1.7

    \a enabled.
*/
void QXmppConfiguration::setUseSasl2Authentication(bool enabled)
{
    d->useSasl2Authentication = enabled;
}

/*!
    Returns whether to use FAST token-based authentication from \xep{0484}{Fast Authentication
    Streamlining Tokens} if available.

    Note that FAST requires a valid SASL 2 user-agent to be set.

    \since QXmpp 1.8
*/
bool QXmppConfiguration::useFastTokenAuthentication() const
{
    return d->useFastTokenAuthentication;
}

/*!
    Sets whether to use FAST token-based authentication from \xep{0484}{Fast Authentication
    Streamlining Tokens} if available.

    Note that FAST requires a valid SASL 2 user-agent to be set.

    \since QXmpp 1.8

    \a useFast.
*/
void QXmppConfiguration::setUseFastTokenAuthentication(bool useFast)
{
    d->useFastTokenAuthentication = useFast;
}

/*!
    Returns whether SSL errors (such as certificate validation errors)
    are to be ignored when connecting to the XMPP server.
*/
bool QXmppConfiguration::ignoreSslErrors() const
{
    return d->ignoreSslErrors;
}

/*!
    Specifies whether SSL errors (such as certificate validation errors)
    are to be ignored when connecting to an XMPP server.

    \a value.
*/
void QXmppConfiguration::setIgnoreSslErrors(bool value)
{
    d->ignoreSslErrors = value;
}

/*! Returns whether to make use of SASL authentication. */
bool QXmppConfiguration::useSASLAuthentication() const
{
    return d->useSASLAuthentication;
}

/*!
    Sets whether to make use of SASL authentication.

    \a useSASL.
*/
void QXmppConfiguration::setUseSASLAuthentication(bool useSASL)
{
    d->useSASLAuthentication = useSASL;
}

/*! Returns whether to make use of non-SASL authentication. */
bool QXmppConfiguration::useNonSASLAuthentication() const
{
    return d->useNonSASLAuthentication;
}

/*!
    Sets whether to make use of non-SASL authentication.

    \a useNonSASL.
*/
void QXmppConfiguration::setUseNonSASLAuthentication(bool useNonSASL)
{
    d->useNonSASLAuthentication = useNonSASL;
}

/*!
    Returns the specified security mode for the stream. The default value is
    QXmppConfiguration::TLSEnabled.
*/
QXmppConfiguration::StreamSecurityMode QXmppConfiguration::streamSecurityMode() const
{
    return d->streamSecurityMode;
}

/*!
    Specifies the specified security mode for the stream. The default value is
    QXmppConfiguration::TLSEnabled.

    \a mode.
*/
void QXmppConfiguration::setStreamSecurityMode(
    QXmppConfiguration::StreamSecurityMode mode)
{
    d->streamSecurityMode = mode;
}

/*! Returns the Non-SASL authentication mechanism configuration. */
QXmppConfiguration::NonSASLAuthMechanism QXmppConfiguration::nonSASLAuthMechanism() const
{
    return d->nonSASLAuthMechanism;
}

/*!
    Hints the library the Non-SASL authentication mechanism to be used for authentication.

    \a mech.
*/
void QXmppConfiguration::setNonSASLAuthMechanism(
    QXmppConfiguration::NonSASLAuthMechanism mech)
{
    d->nonSASLAuthMechanism = mech;
}

/*! Returns the preferred SASL authentication mechanism. */
QString QXmppConfiguration::saslAuthMechanism() const
{
    return d->saslAuthMechanism;
}

//                "X-FACEBOOK-PLATFORM", "X-MESSENGER-OAUTH2", "X-OAUTH2"
/*!
    Sets the preferred SASL authentication \a mechanism.

    Valid values: "SCRAM-SHA-256", "SCRAM-SHA-1", "DIGEST-MD5", "PLAIN", "ANONYMOUS",
*/
void QXmppConfiguration::setSaslAuthMechanism(const QString &mechanism)
{
    d->saslAuthMechanism = mechanism;
}

/*!
    Returns the list of disabled SASL mechanisms.

    Those mechanisms are not used by the client, even if no other mechanism is available.

    \since QXmpp 1.7
*/
QList<QString> QXmppConfiguration::disabledSaslMechanisms() const
{
    return d->disabledSaslMechanisms;
}

/*!
    Adds to the list of disabled SASL mechanisms.

    Those mechanisms are not used by the client, even if no other mechanism is available.

    \since QXmpp 1.7

    \a m.
*/
void QXmppConfiguration::addDisabledSaslMechanism(const QString &m)
{
    if (!d->disabledSaslMechanisms.contains(m)) {
        d->disabledSaslMechanisms.push_back(m);
    }
}

/*!
    Sets the list of disabled SASL mechanisms.

    Those mechanisms are not used by the client, even if no other mechanism is available.

    \since QXmpp 1.7

    \a disabled.
*/
void QXmppConfiguration::setDisabledSaslMechanisms(const QList<QString> &disabled)
{
    d->disabledSaslMechanisms = disabled;
}

/*!
    Returns the user-agent used for \xep{0388}{Extensible SASL Profile}.

    If this is empty, no user-agent will be sent.

    \since QXmpp 1.7
*/
std::optional<QXmppSasl2UserAgent> QXmppConfiguration::sasl2UserAgent() const
{
    return d->sasl2UserAgent;
}

/*!
    Sets the user-agent used for \xep{0388}{Extensible SASL Profile}.

    If this is empty, no user-agent will be sent.

    \since QXmpp 1.7

    \a userAgent.
*/
void QXmppConfiguration::setSasl2UserAgent(const std::optional<QXmppSasl2UserAgent> &userAgent)
{
    d->sasl2UserAgent = userAgent;
}

/*!
    Returns the preferred language for the XMPP stream.

    If no locale has been explicitly set, this returns the system locale's BCP 47 language tag
    (e.g. "en", "de-DE").

    The returned value is used as the \c xml:lang attribute on the XMPP stream element, as
    specified in RFC 6120 Section 4.7.4.

    \since QXmpp 1.15
*/
QString QXmppConfiguration::locale() const
{
    if (d->locale.isEmpty()) {
        // Check locale environment variables directly because QLocale::system() may
        // not reflect the user's intended locale if it is not installed on the system.
        for (auto *var : { "LC_ALL", "LC_MESSAGES", "LANG" }) {
            auto value = qEnvironmentVariable(var);
            if (!value.isEmpty() && value != u"C" && value != u"POSIX") {
                return QLocale(value).bcp47Name();
            }
        }

        return QLocale::system().bcp47Name();
    }
    return d->locale;
}

/*!
    Sets the preferred language for the XMPP stream.

    Pass an empty string to reset to auto-detection from the system locale.

    \since QXmpp 1.15

    \a locale.
*/
void QXmppConfiguration::setLocale(const QString &locale)
{
    d->locale = locale;
}

/*!
    Specifies the network proxy used for the connection made by QXmppClient.
    The default value is QNetworkProxy::DefaultProxy that is the proxy is
    determined based on the application proxy set using
    QNetworkProxy::setApplicationProxy().

    \a proxy.
*/
void QXmppConfiguration::setNetworkProxy(const QNetworkProxy &proxy)
{
    d->networkProxy = proxy;
}

/*!
    Returns the specified network proxy.
    The default value is QNetworkProxy::DefaultProxy that is the proxy is
    determined based on the application proxy set using
    QNetworkProxy::setApplicationProxy().
*/
QNetworkProxy QXmppConfiguration::networkProxy() const
{
    return d->networkProxy;
}

/*!
    Specifies the interval in seconds at which keep alive (ping) packets
    will be sent to the server.

    If set to zero, no keep alive packets will be sent.

    The default value is 60 seconds.

    \a secs.
*/
void QXmppConfiguration::setKeepAliveInterval(int secs)
{
    d->keepAliveInterval = secs;
}

/*!
    Returns the keep alive interval in seconds.

    The default value is 60 seconds.
*/
int QXmppConfiguration::keepAliveInterval() const
{
    return d->keepAliveInterval;
}

/*!
    Specifies the maximum time in seconds to wait for a keep alive response
    from the server before considering we are disconnected.

    If set to zero or a value larger than the keep alive interval,
    no timeout will occur.

    The default value is 20 seconds.

    \a secs.
*/
void QXmppConfiguration::setKeepAliveTimeout(int secs)
{
    d->keepAliveTimeout = secs;
}

/*!
    Returns the keep alive timeout in seconds.

    The default value is 20 seconds.
*/
int QXmppConfiguration::keepAliveTimeout() const
{
    return d->keepAliveTimeout;
}

/*!
    Specifies a list of trusted CA certificates.

    \a caCertificates.
*/
void QXmppConfiguration::setCaCertificates(const QList<QSslCertificate> &caCertificates)
{
    d->caCertificates = caCertificates;
}

/*! Returns the a list of trusted CA certificates. */
QList<QSslCertificate> QXmppConfiguration::caCertificates() const
{
    return d->caCertificates;
}

const Credentials &QXmppConfiguration::credentialData() const
{
    return *(d->credentials.d);
}

Credentials &QXmppConfiguration::credentialData()
{
    return *(d->credentials.d);
}
