Add support for XEP-0100 jabber:iq:gateway

This commit is contained in:
Luca Matei Pintilie
2026-08-02 18:12:11 +02:00
parent ad14748a30
commit dbafe37e13
17 changed files with 323 additions and 5 deletions
+1
View File
@@ -1,6 +1,7 @@
#cmakedefine USE_DATABASE
#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST
#cmakedefine LIBIDN_FOUND
#cmakedefine CURL_FOUND
#cmakedefine SYSTEMD_FOUND
#cmakedefine POLLER ${POLLER}
#cmakedefine BOTAN_FOUND
+68
View File
@@ -0,0 +1,68 @@
#include <irc/irc_uri.hpp>
#include <utils/split.hpp>
#include <cstdio>
#include <cstring>
IrcUri::IrcUri(const std::string &uri) {
#ifdef CURL_FOUND
this->url = curl_url();
curl_url_set(this->url, CURLUPART_URL, uri.c_str(), CURLU_NON_SUPPORT_SCHEME | CURLU_NO_AUTHORITY);
#endif
(void)uri;
}
IrcUri::~IrcUri(void) {
#ifdef CURL_FOUND
curl_url_cleanup(this->url);
#endif
}
const std::optional<std::string> IrcUri::get_server(void) const {
#ifdef CURL_FOUND
char *server;
CURLUcode rc = curl_url_get(this->url, CURLUPART_HOST, &server, 0);
if (rc) {
return std::nullopt;
}
return server;
#else
return std::nullopt;
#endif // CURL_FOUND
};
const std::optional<std::string> IrcUri::get_entity(void) const {
#ifdef CURL_FOUND
char *entity;
std::string output;
CURLUcode rc;
rc = curl_url_get(this->url, CURLUPART_PATH, &entity, CURLU_URLDECODE);
if (rc)
return std::nullopt;
output = entity;
if (!output.empty())
output.erase(0, 1);
if (output.empty()) {
rc = curl_url_get(this->url, CURLUPART_FRAGMENT, &entity, CURLU_URLDECODE);
if (rc)
return std::nullopt;
output = entity;
output.insert(0, 1, '#');
}
return IrcUri::parse_entity(output);
#else
return std::nullopt;
#endif // CURL_FOUND
};
const std::string IrcUri::parse_entity(const std::string entity) {
const std::vector<std::string> entity_name_and_type =
utils::split(entity, ',');
if (entity_name_and_type.size() > 0)
return entity_name_and_type.at(0);
return entity;
};
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <biboumi.h>
#include <optional>
#include <xmpp/jid.hpp>
#include <string>
#include <curl/urlapi.h>
/**
* Parses and keeps information about one IRC URI
*
* https://datatracker.ietf.org/doc/html/draft-butcher-irc-url-04
*
* Requires CURL
*/
class IrcUri
{
public:
explicit IrcUri(const std::string &uri);
~IrcUri(void);
IrcUri(const IrcUri&) = delete;
IrcUri(IrcUri&&) = delete;
IrcUri& operator=(const IrcUri&) = delete;
IrcUri& operator=(IrcUri&&) = delete;
const std::optional<std::string> get_server(void) const;
const std::optional<std::string> get_entity(void) const;
private:
#ifdef CURL_FOUND
CURLU *url;
#endif
static const std::string parse_entity(const std::string entity);
};
+89 -3
View File
@@ -1,19 +1,23 @@
#include <xmpp/biboumi_component.hpp>
#include <utils/empty_if_fixed_server.hpp>
#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <utils/tolower.hpp>
#include <logger/logger.hpp>
#include <xmpp/adhoc_command.hpp>
#include <xmpp/biboumi_adhoc_commands.hpp>
#include <xmpp/xmpp_component.hpp>
#include <bridge/list_element.hpp>
#include <utils/encoding.hpp>
#include <config/config.hpp>
#include <utils/time.hpp>
#include <xmpp/jid.hpp>
#include <irc/irc_uri.hpp>
#include <optional>
#include <string>
#include <stdexcept>
#include <iostream>
#include <cstdlib>
@@ -449,9 +453,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
// the scopeguard.
std::string error_type("cancel");
std::string error_name("internal-server-error");
utils::ScopeGuard stanza_error([this, &from, &to_str, &id, &error_type, &error_name](){
std::string error_text("");
utils::ScopeGuard stanza_error([this, &from, &to_str, &id, &error_type, &error_name, &error_text](){
this->send_stanza_error("iq", from, to_str, id,
error_type, error_name, "");
error_type, error_name, error_text);
});
try {
if (type == "set")
@@ -518,6 +523,21 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
this->send_stanza(response);
stanza_error.disable();
}
#ifdef CURL_FOUND
else if ((query = stanza.get_child("query", GATEWAY_NS))) {
std::optional<Stanza> stanza =
this->handle_iq_gateway(query, &to, from, id);
if (stanza.has_value()) {
this->send_stanza(stanza.value());
stanza_error.disable();
} else {
error_name = "bad-request";
error_type = "modify";
error_text = "Invalid input. Check that the provided link is a valid irc URI";
return;
}
}
#endif // CURL_FOUND
#ifdef USE_DATABASE
else if ((query = stanza.get_child("query", MAM_NS)))
{
@@ -673,6 +693,28 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
}
stanza_error.disable();
}
#ifdef CURL_FOUND
else if ((query = stanza.get_child("query", GATEWAY_NS)))
{
Stanza iq("iq");
{
iq["from"] = to_str;
iq["to"] = from;
iq["id"] = id;
iq["type"] = "result";
XmlSubNode query(iq, GATEWAY_NS, "query");
{
query["xmlns"] = GATEWAY_NS;
XmlSubNode desc(query, "desc");
desc.set_inner("Enter an IRC URI. For example "
"irc://irc.example.com/#channame");
XmlSubNode prompt(query, "prompt");
prompt.set_inner("IRC URI");
};
}
this->send_stanza(iq);
}
#endif // CURL_FOUND
#ifdef USE_DATABASE
else if ((query = stanza.get_child("query", MUC_OWNER_NS)))
{
@@ -736,6 +778,50 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
error_name = "feature-not-implemented";
}
#ifdef CURL_FOUND
std::optional<Stanza>
BiboumiComponent::handle_iq_gateway(const Stanza *stanza, const Jid *from,
const std::string to,
const std::string id) {
const XmlNode *prompt = stanza->get_child("prompt", GATEWAY_NS);
const std::string inner = prompt->get_inner();
std::string local;
bool is_irc_uri =
(inner.starts_with("irc://") || inner.starts_with("ircs://"));
if (is_irc_uri) {
IrcUri irc_uri(inner);
const std::optional<std::string> entity = irc_uri.get_entity();
const std::optional<std::string> server = irc_uri.get_server();
const std::string irc_server = utils::empty_if_fixed_server("%" + server.value_or(""));
if (irc_server != "%")
local = entity.value_or("") + irc_server;
} else {
Iid i(inner, this->get_user_bridge(to));
const std::string iid_local = i.get_local();
if (!iid_local.empty())
local = iid_local + utils::empty_if_fixed_server("%" + i.get_server());
}
if (local.empty())
return std::nullopt;
Jid j(local + "@" + from->domain);
Stanza iq("iq");
{
iq["from"] = from->full();
iq["to"] = to;
iq["id"] = id;
iq["type"] = "result";
XmlSubNode query(iq, GATEWAY_NS, "query");
{
query["xmlns"] = GATEWAY_NS;
XmlSubNode jid(query, "jid");
jid.set_inner(j.full());
};
}
return iq;
}
#endif // CURL_FOUND
#ifdef USE_DATABASE
bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
{
+3
View File
@@ -90,6 +90,9 @@ public:
void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick);
private:
void send_invitation_from_fulljid(const std::string& room_target, const std::string& jid_to, const std::string& from);
#ifdef CURL_FOUND
std::optional<Stanza> handle_iq_gateway(const Stanza *stanza, const Jid *from, const std::string to, const std::string id);
#endif
public:
void accept_subscription(const std::string& from, const std::string& to);
void ask_subscription(const std::string& from, const std::string& to);
+1
View File
@@ -40,6 +40,7 @@
#define STABLE_MUC_ID_NS "http://jabber.org/protocol/muc#stable_id"
#define SELF_PING_FLAG MUC_NS"#self-ping-optimization"
#define MESSAGE_CORRECT_NS "urn:xmpp:message-correct:0"
#define GATEWAY_NS "jabber:iq:gateway"
/**
* An XMPP component, communicating with an XMPP server using the protocole