From ee004ca9c05ab66af812f6e8f0d915810e6ad13e Mon Sep 17 00:00:00 2001 From: LDA Date: Mon, 22 Jul 2024 18:02:24 +0200 Subject: [PATCH] [ADD/WIP] VCard4, slightly more PEPwerk --- src/Parsee/Data.c | 15 +++ src/XMPPThread/PEP.c | 126 ++++++++++++++++++ .../{PubsubAvatar.c => PEPs/Avatar.c} | 18 +-- src/XMPPThread/PEPs/VCard.c | 116 ++++++++++++++++ src/XMPPThread/Pubsub.c | 1 + src/XMPPThread/ReListener.c | 14 +- src/XMPPThread/Stanzas/IQ.c | 13 +- src/XMPPThread/Stanzas/Message.c | 21 +-- src/XMPPThread/internal.h | 14 ++ src/include/Parsee.h | 7 + 10 files changed, 313 insertions(+), 32 deletions(-) create mode 100644 src/XMPPThread/PEP.c rename src/XMPPThread/{PubsubAvatar.c => PEPs/Avatar.c} (89%) create mode 100644 src/XMPPThread/PEPs/VCard.c diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 10cd0de..5affa32 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -881,3 +881,18 @@ ParseeAchievement(const char *func, const char *msg, bool die) abort(); } } +char * +ParseeGenerateMTO(char *common_id) +{ + char *matrix_to; + if (!common_id) + { + return NULL; + } + + common_id = HttpUrlEncode(common_id); + matrix_to = StrConcat(2, "https://matrix.to/#/", common_id); + Free(common_id); + + return matrix_to; +} diff --git a/src/XMPPThread/PEP.c b/src/XMPPThread/PEP.c new file mode 100644 index 0000000..1dc36ea --- /dev/null +++ b/src/XMPPThread/PEP.c @@ -0,0 +1,126 @@ +#include "XMPPThread/internal.h" + +#include +#include +#include +#include + +#include + +struct PEPManager { + pthread_mutex_t lock; + + ParseeData *data; + HashMap *node_table; + + void *cookie; +}; + +PEPManager * +CreatePEPManager(ParseeData *data, void *cookie) +{ + PEPManager *ret; + if (!data) + { + return NULL; + } + + ret = Malloc(sizeof(*ret)); + ret->cookie = cookie; + ret->data = data; + ret->node_table = HashMapCreate(); + pthread_mutex_init(&ret->lock, NULL); + + return ret; +} +void * +PEPManagerCookie(PEPManager *manager) +{ + return manager ? manager->cookie : NULL; +} +void +PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event) +{ + if (!manager || !node || !event) + { + return; + } + + pthread_mutex_lock(&manager->lock); + HashMapSet(manager->node_table, node, event); + pthread_mutex_unlock(&manager->lock); +} + +static bool +PEPManagerHandleEvent(PEPManager *manager, XMLElement *stanza) +{ + PEPEvent call = NULL; + XMLElement *event, *ps, *ev; + size_t i; + if (!manager || !stanza) + { + return false; + } +#define PEP_NS "http://jabber.org/protocol/pubsub" + if (!(ps = XMLookForTKV(stanza, "pubsub", "xmlns", PEP_NS)) && + !(ev = XMLookForTKV(stanza, "event", "xmlns", PEP_NS "#event"))) + { + return false; + } + event = ps ? ps : ev; + + for (i = 0; i < ArraySize(event->children); i++) + { + XMLElement *items = ArrayGet(event->children, i); + char *node = HashMapGet(items->attrs, "node"); + + if ((call = HashMapGet(manager->node_table, node))) + { + size_t j; + /* Use the callback over all items */ + if (ev) + { + for (j = 0; j < ArraySize(items->children); j++) + { + call(manager, stanza, ArrayGet(items->children, j)); + } + return true; + } + + /* ... or over "items" specifically. */ + call(manager, stanza, items); + return true; + } + } + + return false; +} + +bool +PEPManagerHandle(PEPManager *manager, XMLElement *stanza) +{ + if (!manager || !stanza) + { + return false; + } + + /* Check if it is a PEP stanza */ + if (PEPManagerHandleEvent(manager, stanza)) + { + return true; + } + + return false; +} +void +DestroyPEPManager(PEPManager *manager) +{ + if (!manager) + { + return; + } + + pthread_mutex_destroy(&manager->lock); + HashMapFree(manager->node_table); + Free(manager); +} diff --git a/src/XMPPThread/PubsubAvatar.c b/src/XMPPThread/PEPs/Avatar.c similarity index 89% rename from src/XMPPThread/PubsubAvatar.c rename to src/XMPPThread/PEPs/Avatar.c index 7b63d05..819bcd0 100644 --- a/src/XMPPThread/PubsubAvatar.c +++ b/src/XMPPThread/PEPs/Avatar.c @@ -1,14 +1,15 @@ #include "XMPPThread/internal.h" -#include -#include -#include - #include #include #include -/* Manages an avatar metadata pubsub item */ +#include +#include +#include +#include +#include + static XMLElement * CreateAvatarRequest(char *from, char *to, char *avatar_id) { @@ -38,9 +39,11 @@ CreateAvatarRequest(char *from, char *to, char *avatar_id) void -ManageProfileItem(ParseeData *args, XMLElement *item, XMLElement *stanza, XMPPThread *thr) +PEPAvatarEvent(PEPManager *m, XMLElement *stanza, XMLElement *item) { - XMPPComponent *jabber = args->jabber; + XMPPThreadInfo *info = PEPManagerCookie(m); + XMPPComponent *jabber = info->jabber; + ParseeData *args = info->args; DbRef *avatars; HashMap *json; @@ -82,4 +85,3 @@ ManageProfileItem(ParseeData *args, XMLElement *item, XMLElement *stanza, XMPPTh end: DbUnlock(args->db, avatars); } - diff --git a/src/XMPPThread/PEPs/VCard.c b/src/XMPPThread/PEPs/VCard.c new file mode 100644 index 0000000..096b2d0 --- /dev/null +++ b/src/XMPPThread/PEPs/VCard.c @@ -0,0 +1,116 @@ +#include "XMPPThread/internal.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define AddFieldBase(type) \ + XMLElement *field, *text, *text_data; \ + field = XMLCreateTag(name); \ + text = XMLCreateTag(type); \ + text_data = XMLCreateText(value); \ + \ + XMLAddChild(vcard, field); \ + XMLAddChild(field, text); \ + XMLAddChild(text, text_data) +static void +AddTextField(XMLElement *vcard, char *name, char *value) +{ + AddFieldBase("text"); +} +static void +AddURIField(XMLElement *vcard, char *name, char *value) +{ + AddFieldBase("uri"); +} +#undef AddFieldBase + +void +PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item) +{ + XMPPThreadInfo *info = PEPManagerCookie(m); + XMPPComponent *jabber = info->jabber; + ParseeData *data = info->args; + char *from = HashMapGet(stanza->attrs, "from"); + char *to = HashMapGet(stanza->attrs, "to"); + char *id = HashMapGet(stanza->attrs, "id"); + XMLElement *reply; + + reply = XMLCreateTag("iq"); + XMLAddAttr(reply, "type", "result"); + XMLAddAttr(reply, "id", id); + XMLAddAttr(reply, "from", to); + XMLAddAttr(reply, "to", from); + { + XMLElement *pubsub, *items, *item; + pubsub = XMLCreateTag("pubsub"); + items = XMLCreateTag("items"); + item = XMLCreateTag("item"); + XMLAddChild(reply, pubsub); + XMLAddChild(pubsub, items); + XMLAddChild(items, item); + XMLAddAttr(pubsub, "xmlns", "http://jabber.org/protocol/pubsub"); + XMLAddAttr(items, "node", "urn:xmpp:vcard4"); + { + XMLElement *vcard = XMLCreateTag("vcard"); + XMLAddAttr(vcard, "xmlns", "urn:ietf:params:xml:ns:vcard-4.0"); + + if (!strncmp(to, "parsee", 6)) + { + AddTextField(vcard, "title", "System user"); + + AddTextField(vcard, "fn", "Parsee Mizuhashi"); + AddTextField(vcard, "nickname", "parsee"); + AddTextField(vcard, "nickname", "that annoying bridge"); + AddTextField(vcard, "nickname", "green-eyed monster"); + + AddURIField(vcard, "url", REPOSITORY); + AddURIField(vcard, "url", "https://kappach.at/parsee"); + + AddTextField(vcard, "note", "This is the Parsee user account."); + } + else + { + /* TODO: Get the user's info(over the Matrix profile API if + * the server shows support for it.) */ + char *mxid = ParseeDecodeMXID(to); + char *name = ASGetName(data->config, NULL, mxid); + char *m_to = ParseeGenerateMTO(mxid); + + AddTextField(vcard, "title", "Matrix user"); + + AddTextField(vcard, "fn", name); + AddTextField(vcard, "nickname", mxid); + + AddURIField(vcard, "url", REPOSITORY); + AddURIField(vcard, "url", "https://kappach.at/parsee"); + AddURIField(vcard, "url", "https://matrix.org"); + AddURIField(vcard, "url", m_to); + + AddTextField( + vcard, + "note", + "This is a bridged Matrix user, from Parsee." + ); + Free(mxid); + Free(name); + Free(m_to); + } + + XMLAddChild(item, vcard); + } + } + + pthread_mutex_lock(&jabber->write_lock); + XMLEncode(jabber->stream, reply); + StreamFlush(jabber->stream); + pthread_mutex_unlock(&jabber->write_lock); + XMLFreeElement(reply); +} diff --git a/src/XMPPThread/Pubsub.c b/src/XMPPThread/Pubsub.c index e1300bb..c2ec8af 100644 --- a/src/XMPPThread/Pubsub.c +++ b/src/XMPPThread/Pubsub.c @@ -6,6 +6,7 @@ #include #include +/* TODO: This should be in PEP.c */ XMLElement * CreatePubsubRequest(char *from, char *to, char *node) { diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 562d3a2..76d9e5a 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -124,7 +124,7 @@ ParseeXMPPThread(void *argp) /* Initialise the await table */ await_table = HashMapCreate(); - /* Initialise the command manager, and add all ad-hoc commands */ + /* Initialise the managers, and add all handlers. */ info.m = XMPPCreateManager(args); { XMPPCommand *cmd; @@ -137,6 +137,17 @@ ParseeXMPPThread(void *argp) XMPPCOMMANDS #undef XMPP_COMMAND } + info.pep_manager = CreatePEPManager(args, &info); + { + PEPManagerAddEvent( + info.pep_manager, + "urn:xmpp:avatar:metadata", PEPAvatarEvent + ); + PEPManagerAddEvent( + info.pep_manager, + "urn:xmpp:vcard4", PEPVCardEvent + ); + } /* Initialise the FIFO */ info.stanzas = ArrayCreate(); @@ -221,6 +232,7 @@ ParseeXMPPThread(void *argp) pthread_mutex_destroy(&info.lock); + DestroyPEPManager(info.pep_manager); XMPPFreeManager(info.m); return NULL; } diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index c219142..5e0f151 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -120,6 +120,8 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp"); + + /* TODO: Move this to PEP */ XMLElement *event = XMLookForTKV(stanza, "pubsub", "xmlns", "http://jabber.org/protocol/pubsub" ); @@ -425,10 +427,15 @@ IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } #undef DISCO -void IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) +void +IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { - char *type; - type = HashMapGet(stanza->attrs, "type"); + char *type = HashMapGet(stanza->attrs, "type"); + + if (PEPManagerHandle(thr->info->pep_manager, stanza)) + { + return; + } #define OnType(ctyp, callback) do \ { \ if (StrEquals(type, #ctyp)) \ diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index f6ef930..f6d5180 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -17,7 +17,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) XMLElement *reactions = NULL; XMLElement *body = NULL; XMLElement *data = NULL; - XMLElement *event = NULL; char *to, *room, *from, *from_matrix, *decode_from; char *mroom_id = NULL; @@ -64,25 +63,7 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } body = XMLookForUnique(stanza, "body"); - event = XMLookForTKV(stanza, "event", - "xmlns", "http://jabber.org/protocol/pubsub#event" - ); - if (event) - { - size_t i; - XMLElement *items = - XMLookForTKV(event, "items", "node", "urn:xmpp:avatar:metadata"); - if (items) - { - for (i = 0; i < ArraySize(items->children); i++) - { - ManageProfileItem( - args, ArrayGet(items->children, i), - stanza, thr - ); - } - } - } + PEPManagerHandle(thr->info->pep_manager, stanza); #define CHAT_STATES "http://jabber.org/protocol/chatstates" if (XMLookForTKV(stanza, "composing", "xmlns", CHAT_STATES)) diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 08951fe..c24b528 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -32,6 +32,9 @@ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("component", "generic", "Parsee's component") +typedef struct PEPManager PEPManager; +typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); + typedef struct XMPPIdentity { char *category, *type, *lang, *name; } XMPPIdentity; @@ -46,6 +49,7 @@ typedef struct XMPPThreadInfo { ParseeData *args; XMPPComponent *jabber; XMPPCommandManager *m; + PEPManager *pep_manager; struct XMPPThread *dispatchers; size_t available_dispatchers; @@ -80,3 +84,13 @@ bool ServerHasXEP421(ParseeData *data, char *from); char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force); #define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL) + +PEPManager * CreatePEPManager(ParseeData *data, void *cookie); +void * PEPManagerCookie(PEPManager *manager); +void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event); +bool PEPManagerHandle(PEPManager *manager, XMLElement *stanza); +void DestroyPEPManager(PEPManager *manager); + +/* PEP callbacks for the handler */ +void PEPAvatarEvent(PEPManager *m, XMLElement *stanza, XMLElement *item); +void PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 483109e..10f9cec 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -255,4 +255,11 @@ extern char * ParseeMXID(ParseeData *data); extern void ParseeAchievement(const char *func, const char *msg, bool die); #define Achievement(msg, die) ParseeAchievement(__func__, msg, die) +/** Generates a 'matrix.to' URL from a Matrix ID, to be used for + * linking to a room, user, etc... + * ---------------------- + * Returns: A Matrix URL[LA:HEAP] + * Thrasher: Free */ +extern char * ParseeGenerateMTO(char *common_id); + #endif