forked from qxmpp-project/qxmpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
draft: Add QXmppUri for XMPP URI parsing
- Loading branch information
Showing
3 changed files
with
487 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.