Skip to content

Commit

Permalink
draft: Add QXmppUri for XMPP URI parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
lnjX committed Jul 22, 2024
1 parent cccc0da commit d34fa90
Show file tree
Hide file tree
Showing 3 changed files with 487 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ set(INSTALL_HEADER_FILES
base/QXmppTrustMessages.h
base/QXmppUserTuneItem.h
base/QXmppUtils.h
base/QXmppUri.h
base/QXmppVCardIq.h
base/QXmppVersionIq.h
base/compat/QXmppSessionIq.h
Expand Down Expand Up @@ -230,6 +231,7 @@ set(SOURCE_FILES
base/QXmppTask.cpp
base/QXmppThumbnail.cpp
base/QXmppTrustMessages.cpp
base/QXmppUri.cpp
base/QXmppUserTuneItem.cpp
base/QXmppUtils.cpp
base/QXmppVCardIq.cpp
Expand Down
355 changes: 355 additions & 0 deletions src/base/QXmppUri.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
// SPDX-FileCopyrightText: 2019 Linus Jahn <[email protected]>
// SPDX-FileCopyrightText: 2019 Melvin Keskin <[email protected]>
// SPDX-FileCopyrightText: 2020 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppUri.h"

#include "QXmppUtils_p.h"

#include "StringLiterals.h"

#include <array>

#include <QUrlQuery>

using namespace QXmpp::Private;
using namespace QXmpp::Uri;

constexpr QStringView SCHEME = u"xmpp";
constexpr QStringView PREFIX = u"xmpp:";
constexpr QChar QUERY_ITEM_DELIMITER = u';';
constexpr QChar QUERY_ITEM_KEY_DELIMITER = u'=';

// Query types representing actions, e.g. "join" in
// "xmpp:[email protected]?join" for joining a group chat
constexpr std::array<QStringView, 18> QUERY_TYPES = {
QStringView(),
u"command",
u"disco",
u"invite",
u"join",
u"login",
u"message",
u"pubsub",
u"recvfile",
u"register",
u"remove",
u"roster",
u"sendfile",
u"subscribe",
u"trust-message",
u"unregister",
u"unsubscribe",
u"vcard"
};

// QXmppMessage types as strings
constexpr std::array<QStringView, 5> MESSAGE_TYPES = {
u"error",
u"normal",
u"chat",
u"groupchat",
u"headline"
};

///
/// Adds a key-value pair to a query if the value is not empty.
///
/// \param query to which the key-value pair is added
/// \param key key of the value
/// \param value value of the key
///
static void addKeyValuePairToQuery(QUrlQuery &query, const QString &key, QStringView value)
{
if (!value.isEmpty()) {
query.addQueryItem(key, value.toString());
}
}

///
/// Extracts the fully-encoded value of a query's key-value pair.
///
/// \param query query containing the key-value pair
/// \param key of the searched value
///
/// @return the value of the key
///
static QString queryItemValue(const QUrlQuery &query, const QString &key)
{
return query.queryItemValue(key, QUrl::FullyDecoded);
}

namespace QXmpp::Uri {

struct NoQuery { };

} // namespace QXmpp::Uri

static void serializeUrlQuery(const NoQuery &, QUrlQuery &) { }

static void serializeUrlQuery(const Message &message, QUrlQuery &query)
{
query.addQueryItem(u"message"_s, {});

addKeyValuePairToQuery(query, u"from"_s, message.from);
addKeyValuePairToQuery(query, u"id"_s, message.id);
if (message.type) {
addKeyValuePairToQuery(query, u"type"_s, MESSAGE_TYPES.at(size_t(*message.type)));
}
addKeyValuePairToQuery(query, QStringLiteral("subject"), message.subject);
addKeyValuePairToQuery(query, QStringLiteral("body"), message.body);
addKeyValuePairToQuery(query, QStringLiteral("thread"), message.thread);
}

static void serializeUrlQuery(const Login &login, QUrlQuery &query)
{
query.addQueryItem(u"login"_s, {});
addKeyValuePairToQuery(query, QStringLiteral("password"), login.password);
}

static void serializeUrlQuery(const TrustMessage &trustMessage, QUrlQuery &query)
{
query.addQueryItem(u"trust-message"_s, {});

addKeyValuePairToQuery(query, QStringLiteral("encryption"), trustMessage.encryption);

for (auto &identifier : trustMessage.trustKeyIds) {
addKeyValuePairToQuery(query, QStringLiteral("trust"), identifier);
}

for (auto &identifier : trustMessage.distrustKeyIds) {
addKeyValuePairToQuery(query, QStringLiteral("distrust"), identifier);
}
}

Command parseCommandQuery(const QUrlQuery &q)
{
return {
queryItemValue(q, u"node"_s),
queryItemValue(q, u"action"_s),
};
}

Invite parseInviteQuery(const QUrlQuery &q)
{
return {
queryItemValue(q, u"invite"_s),
queryItemValue(q, u"password"_s),
};
}

Join parseJoinQuery(const QUrlQuery &q)
{
return {
queryItemValue(q, u"password"_s),
};
}

Login parseLoginQuery(const QUrlQuery &q)
{
return {
queryItemValue(q, u"login"_s),
};
}

Message parseMessageQuery(const QUrlQuery &q)
{
Message message;
message.subject = queryItemValue(q, u"subject"_s);
message.body = queryItemValue(q, u"body"_s);
message.thread = queryItemValue(q, u"thread"_s);
message.id = queryItemValue(q, u"id"_s);
message.from = queryItemValue(q, u"from"_s);
message.type = enumFromString<QXmppMessage::Type>(MESSAGE_TYPES, queryItemValue(q, u"type"_s));
}

TrustMessage parseTrustMessageQuery(const QUrlQuery &q)
{
return TrustMessage {
queryItemValue(q, QStringLiteral("encryption")),
q.allQueryItemValues(QStringLiteral("trust"), QUrl::FullyDecoded),
q.allQueryItemValues(QStringLiteral("distrust"), QUrl::FullyDecoded),
};
}

struct QXmppUriPrivate : QSharedData {
QString jid;
std::any query;
};

///
/// Parses the URI from a string.
///
/// \param input string which may present an XMPP URI
///
QXmppUri::QXmppUri()
: d(new QXmppUriPrivate)
{
}

QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppUri)

std::optional<QXmppUri> QXmppUri::fromString(const QString &input)
{
QUrl url(input);
if (!url.isValid() || url.scheme() != SCHEME) {
return {};
}

QXmppUri uri;

QString jid = url.path();

if (!url.hasQuery()) {
return {};
}
QUrlQuery urlQuery;
urlQuery.setQueryDelimiters(QUERY_ITEM_KEY_DELIMITER, QUERY_ITEM_DELIMITER);
urlQuery.setQuery(url.query(QUrl::FullyEncoded));

// Check if there is at least one query item.
if (!urlQuery.isEmpty()) {
auto queryItems = urlQuery.queryItems();
Q_ASSERT(!queryItems.isEmpty());
auto [queryString, queryValue] = queryItems.first();

if (!queryValue.isEmpty()) {
// invalid XMPP URI: first query query pair must have only key, no value
return {};
}

if (queryString == u"command") {
} else if (queryString == u"invite") {
uri.d->query = parseInviteQuery(urlQuery);
} else if (queryString == u"join") {
uri.d->query = parseJoinQuery(urlQuery);
} else if (queryString == u"login") {
uri.d->query = parseLoginQuery(urlQuery);
} else if (queryString == u"message") {
uri.d->query = parseMessageQuery(urlQuery);
} else if (queryString == u"register") {
uri.d->query = Register();
} else if (queryString == u"remove") {
uri.d->query = Remove();
} else if (queryString == u"roster") {
uri.d->query = parseRosterQuery(urlQuery);
} else if (queryString == u"subscribe") {
uri.d->query = Subscribe();
} else if (queryString == u"trust-message") {
uri.d->query = parseTrustMessageQuery(urlQuery);
} else if (queryString == u"unregister") {
uri.d->query = Unregister();
} else if (queryString == u"unsubscribe") {
uri.d->query = Unsubscribe();
} else if (queryString == u"vcard") {
} else {
uri.d->query = parseCustomQuery(urlQuery);
}
/*
None,
Command,
Disco,
Invite,
Join,
Login,
Message,
PubSub,
RecvFile,
Register,
Remove,
Roster,
SendFile,
Subscribe,
TrustMessage,
Unregister,
Unsubscribe,
VCard,
*/
}

return uri;
}

template<typename T>
bool serialize(const std::any &query, QUrlQuery &urlQuery)
{
if (query.type() == typeid(T)) {
serializeUrlQuery(std::any_cast<T>(query), urlQuery);
return true;
}
return false;
}

///
/// Serializes this URI to a string.
///
/// \return this URI as a string
///
QString QXmppUri::toString()
{
QUrl url;
url.setScheme(SCHEME.toString());
url.setPath(d->jid);

// add URI query
QUrlQuery urlQuery;
urlQuery.setQueryDelimiters(QUERY_ITEM_KEY_DELIMITER, QUERY_ITEM_DELIMITER);

if (d->query.has_value()) {
serialize<Command>(d->query, urlQuery) ||
serialize<Invite>(d->query, urlQuery) ||
serialize<Join>(d->query, urlQuery) ||
serialize<Login>(d->query, urlQuery) ||
serialize<Message>(d->query, urlQuery) ||
serialize<Unregister>(d->query, urlQuery) ||
serialize<Unsubscribe>(d->query, urlQuery) ||
serialize<Register>(d->query, urlQuery) ||
serialize<Remove>(d->query, urlQuery) ||
serialize<Roster>(d->query, urlQuery) ||
serialize<Subscribe>(d->query, urlQuery) ||
serialize<TrustMessage>(d->query, urlQuery) ||
serialize<CustomQuery>(d->query, urlQuery);
}

url.setQuery(urlQuery);

return QString::fromUtf8(url.toEncoded(QUrl::FullyEncoded));
}

///
/// Returns the JID this URI is about.
///
/// This can also be e.g. a MUC room in case of a Join action.
///
QString QXmppUri::jid() const
{
return d->jid;
}

///
/// Sets the JID this URI links to.
///
/// \param jid JID to be set
///
void QXmppUri::setJid(const QString &jid)
{
d->jid = jid;
}

///
/// Returns the query of the URI.
///
/// It may be empty (has_value() returns false). Possible URI types are available in the namespace
/// QXmpp::Uri.
///
std::any QXmppUri::query() const
{
return d->query;
}

void QXmppUri::setQuery(std::any &&query)
{
d->query = std::move(query);
}
Loading

0 comments on commit d34fa90

Please sign in to comment.