[ADD/WIP] XMPP->Matrix avatar, start bridging bans

I still hate XEP-0084.
This commit is contained in:
LDA 2024-07-05 03:10:43 +02:00
commit 1f658ece76
9 changed files with 587 additions and 27 deletions

View file

@ -2,6 +2,7 @@
#include <pthread.h>
#include <string.h>
#include <ctype.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Base64.h>
@ -10,15 +11,21 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <StringStream.h>
#include <Matrix.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
/* TODO: Rewrite this avatar code.
* XEP-0084 sucks. */
#define IQ_ADVERT \
AdvertiseSimple("http://jabber.org/protocol/chatstates") \
AdvertiseSimple("http://jabber.org/protocol/caps") \
AdvertiseSimple("urn:xmpp:avatar:metadata+notify") \
AdvertiseSimple("urn:xmpp:avatar:metadata") \
AdvertiseSimple("urn:xmpp:avatar:data+notify") \
AdvertiseSimple("urn:xmpp:avatar:data") \
AdvertiseSimple("urn:xmpp:message-correct:0") \
AdvertiseSimple("urn:xmpp:reactions:0") \
AdvertiseSimple("urn:xmpp:styling:0") \
@ -290,8 +297,104 @@ typedef struct XMPPThread {
XMPPThreadInfo *info;
} XMPPThread;
/* TODO: Clean up all of this. We are currently separating DMs from MUCs,
* where we could unify all our code, and generalise everything. */
/* Manages an avatar metadata pubsub item */
static XMLElement *
CreateAvatarRequest(char *from, char *to, char *avatar_id)
{
XMLElement *iq_req, *pubsub, *items, *item;
char *id;
iq_req = XMLCreateTag("iq");
XMLAddAttr(iq_req, "from", from);
XMLAddAttr(iq_req, "to", to);
XMLAddAttr(iq_req, "id", (id = StrRandom(16)));
XMLAddAttr(iq_req, "type", "get");
pubsub = XMLCreateTag("pubsub");
XMLAddAttr(pubsub, "xmlns", "http://jabber.org/protocol/pubsub");
XMLAddChild(iq_req, pubsub);
items = XMLCreateTag("items");
XMLAddAttr(items, "node", "urn:xmpp:avatar:data");
XMLAddChild(pubsub, items);
item = XMLCreateTag("item");
XMLAddAttr(item, "id", avatar_id);
XMLAddChild(items, item);
Free(id);
return iq_req;
}
static XMLElement *
CreatePubsubRequest(char *from, char *to, char *node)
{
XMLElement *iq_req, *pubsub, *sub;
char *id;
iq_req = XMLCreateTag("iq");
XMLAddAttr(iq_req, "from", from);
XMLAddAttr(iq_req, "to", to);
XMLAddAttr(iq_req, "id", (id = StrRandom(16)));
XMLAddAttr(iq_req, "type", "set");
pubsub = XMLCreateTag("pubsub");
XMLAddAttr(pubsub, "xmlns", "http://jabber.org/protocol/pubsub");
XMLAddChild(iq_req, pubsub);
sub = XMLCreateTag("subscribe");
XMLAddAttr(sub, "node", node);
XMLAddAttr(sub, "jid", from);
XMLAddChild(pubsub, sub);
//Log(LOG_INFO, "Subscribed to %s's %s node", to, node);
Free(id);
return iq_req;
}
static void
ManageProfileItem(ParseeData *args, XMLElement *item, XMLElement *stanza, XMPPThread *thr)
{
XMPPComponent *jabber = args->jabber;
DbRef *avatars;
HashMap *json;
char *publisher = HashMapGet(item->attrs, "publisher");
char *id = HashMapGet(item->attrs, "id");
char *mxid;
avatars = DbLock(args->db, 1, "avatars");
if (!avatars)
{
avatars = DbCreate(args->db, 1, "avatars");
}
json = DbJson(avatars);
mxid = GrabString(json, 1, publisher);
if (mxid && StrEquals(mxid, id))
{
/* We have nothing to do. */
goto end;
}
/* We need to download the media to push it. Let's submit a pubsub request. */
{
char *from = HashMapGet(stanza->attrs, "to");
char *to = HashMapGet(stanza->attrs, "from");
char *url = HashMapGet(item->attrs, "url");
XMLElement *request = CreateAvatarRequest(from, to, id);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, request);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(request);
}
end:
DbUnlock(args->db, avatars);
}
static bool
MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
@ -300,6 +403,7 @@ 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 *chat_id = NULL, *mroom_id = NULL;
@ -312,6 +416,28 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
from = HashMapGet(stanza->attrs, "from");
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
);
}
}
}
#define CHAT_STATES "http://jabber.org/protocol/chatstates"
if (XMLookForTKV(stanza, "composing", "xmlns", CHAT_STATES))
{
@ -395,6 +521,21 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
chat = true;
}
{
char *parsee = StrConcat(2, "parsee@", args->config->component_host);
XMLElement *ps = CreatePubsubRequest(
parsee, decode_from, "urn:xmpp:avatar:metadata"
);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, ps);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(ps);
Free(parsee);
}
pthread_mutex_lock(&thr->info->chk_lock);
if (ParseeVerifyAllStanza(args, stanza) && !replaced)
{
@ -581,10 +722,158 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
}
static char *
TrimBase64(char *b64)
{
char *ret, *tmp;
ret = NULL;
while (*b64)
{
char ch[2] = { *b64, 0 };
if (isspace(*b64))
{
b64++;
continue;
}
tmp = ret;
ret = StrConcat(2, ret, ch);
Free(tmp);
b64++;
}
return ret;
}
static void
IQResult(ParseeData *args, XMLElement *stanza)
{
XMPPComponent *jabber = args->jabber;
XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp");
XMLElement *event = XMLookForTKV(stanza, "pubsub",
"xmlns", "http://jabber.org/protocol/pubsub"
);
if (event)
{
size_t i;
XMLElement *retrieve =
XMLookForTKV(event, "items", "node", "urn:xmpp:avatar:data");
if (retrieve)
{
for (i = 0; i < ArraySize(retrieve->children); i++)
{
XMLElement *item =
ArrayGet(retrieve->children, i);
XMLElement *avatar_data = XMLookForTKV(
item, "data", "xmlns", "urn:xmpp:avatar:data"
);
XMLElement *data = ArrayGet(avatar_data->children, 0);
char *id = HashMapGet(item->attrs, "id");
char *from = HashMapGet(stanza->attrs, "from");
char *base64;
unsigned char *bdata;
size_t length, b64len;
Stream *datastream;
char *mxc, *from_matrix, *jid;
DbRef *avatars;
HashMap *json;
if (!data || !data->data)
{
return;
}
avatars = DbLock(args->db, 1, "avatars");
if (!avatars)
{
avatars = DbCreate(args->db, 1, "avatars");
}
json = DbJson(avatars);
if (StrEquals(GrabString(json, 1, from), id))
{
DbUnlock(args->db, avatars);
return;
}
base64 = TrimBase64(data->data);
b64len = base64 ? strlen(base64) : 0;
length = Base64DecodedSize(base64, b64len);
/* TODO: Bound checks! */
bdata = Base64Decode(base64, b64len);
datastream = StrStreamReaderN(bdata, length);
mxc = ASUpload(args->config, datastream, length);
jid = ParseeLookupJID(from);
from_matrix = ParseeEncodeJID(args->config, jid, false);
ASSetAvatar(args->config, from_matrix, mxc);
JsonValueFree(JsonSet(
json, JsonValueString(id),
1, from)
);
DbUnlock(args->db, avatars);
Free(mxc);
Free(jid);
Free(bdata);
Free(from_matrix);
Free(base64);
StreamClose(datastream);
}
}
}
if (vcard)
{
XMLElement *photo = XMLookForUnique(vcard, "PHOTO");
XMLElement *nickname = XMLookForUnique(vcard, "NICKNAME");
if (nickname)
{
XMLElement *data = ArrayGet(nickname->children, 0);
}
if (photo)
{
XMLElement *binval = XMLookForUnique(photo, "BINVAL");
XMLElement *data = ArrayGet(binval->children, 0);
char *base64;
unsigned char *bdata;
size_t length, b64len;
Stream *datastream;
char *mxc, *from_matrix, *jid;
if (!data || !data->data)
{
return;
}
base64 = data->data;
b64len = base64 ? strlen(base64) : 0;
length = Base64DecodedSize(base64, b64len);
bdata = Base64Decode(base64, b64len);
datastream = StrStreamReaderN(bdata, length);
mxc = ASUpload(args->config, datastream, length);
jid = ParseeLookupJID(HashMapGet(stanza->attrs, "from"));
from_matrix = ParseeEncodeJID(args->config, jid, false);
ASSetAvatar(args->config, from_matrix, mxc);
/* TODO: Check if already set. */
Free(bdata);
StreamClose(datastream);
Free(from_matrix);
Free(jid);
Free(mxc);
}
}
}
static void
IQGet(ParseeData *args, XMLElement *stanza)
{
@ -622,6 +911,7 @@ IQGet(ParseeData *args, XMLElement *stanza)
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
}
@ -634,6 +924,11 @@ IQGet(ParseeData *args, XMLElement *stanza)
}
}
static void
IQError(ParseeData *args, XMLElement *stanza)
{
/* TODO */
}
#undef DISCO
static void
IQStanza(ParseeData *args, XMLElement *stanza)
@ -651,9 +946,30 @@ IQStanza(ParseeData *args, XMLElement *stanza)
while (0)
OnType(get, IQGet);
OnType(error, IQError);
OnType(result, IQResult);
#undef OnType
}
static XMLElement *
CreateVCardRequest(char *from, char *to)
{
XMLElement *vcard_request, *vcard;
char *id;
vcard_request = XMLCreateTag("iq");
XMLAddAttr(vcard_request, "from", from);
XMLAddAttr(vcard_request, "to", to);
XMLAddAttr(vcard_request, "id", (id = StrRandom(16)));
XMLAddAttr(vcard_request, "type", "get");
vcard = XMLCreateTag("vCard");
XMLAddAttr(vcard, "xmlns", "vcard-temp");
XMLAddChild(vcard_request, vcard);
Free(id);
return vcard_request;
}
static void
PresenceStanza(ParseeData *args, XMLElement *stanza)
{
@ -664,35 +980,95 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS)))
{
XMLElement *item = XMLookForUnique(user_info, "item");
XMPPComponent *jabber = args->jabber;
char *jid = item ? HashMapGet(item->attrs, "jid") : NULL;
char *from, *best = jid ? jid : oid;
char *type = HashMapGet(stanza->attrs, "type");
if (StrEquals(type, "unavailable"))
{
/* TODO: Treat as a ban if the role is outcast */
char *room = ParseeGetBridgedRoom(args, stanza);
char *decode_from = ParseeLookupJID(oid);
char *from_matrix = ParseeDecodeMXID(decode_from);
char *affiliation = HashMapGet(item->attrs, "affiliation");
if (!from_matrix || *from_matrix != '@')
{
Free(from_matrix);
from_matrix = ParseeEncodeJID(args->config, oid, true);
}
if (StrEquals(affiliation, "outcast"))
{
ASBan(args->config, room, from_matrix);
}
else
{
ASKick(args->config, room, from_matrix);
}
Free(decode_from);
Free(from_matrix);
Free(room);
}
if (jid)
{
ParseePushJIDTable(oid, jid);
}
from = StrConcat(2, "parsee@", args->config->component_host);
Free(from);
}
else if (vc)
{
XMLElement *photo = XMLookForUnique(vc, "photo");
XMLElement *p_dat = photo ? ArrayGet(photo->children, 0) : NULL;
char *room_id = NULL, *chat_id = NULL;
XMLElement *vcard_request;
XMPPComponent *jabber = args->jabber;
char *from;
DbRef *avatars;
HashMap *json;
char *avatar_id;
if (!p_dat)
{
return;
}
chat_id = ParseeGetFromMUCID(args, oid);
room_id = ParseeGetRoomID(args, chat_id);
if (!room_id)
avatars = DbLock(args->db, 1, "avatars");
if (!avatars)
{
Free(chat_id);
Free(room_id);
avatars = DbCreate(args->db, 1, "avatars");
}
/* TODO: Get the media, and shove it to the room */
Free(chat_id);
Free(room_id);
json = DbJson(avatars);
avatar_id = GrabString(json, 1, oid);
if (StrEquals(avatar_id, p_dat->data))
{
DbUnlock(args->db, avatars);
return;
}
JsonValueFree(JsonSet(
json, JsonValueString(p_dat->data),
1, oid)
);
DbUnlock(args->db, avatars);
from = StrConcat(2, "parsee@", args->config->component_host);
vcard_request = CreateVCardRequest(
from, HashMapGet(stanza->attrs, "from")
);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, vcard_request);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(vcard_request);
Free(from);
}
#undef MUC_USER_NS
}
@ -728,13 +1104,12 @@ XMPPDispatcher(void *argp)
XMLElement *stanza = RetrieveStanza(thread);
if (!stanza)
{
UtilSleepMillis(10);
UtilSleepMillis(1);
continue;
}
if (StrEquals(stanza->name, "presence"))
{
/* TODO: Manage presence */
PresenceStanza(args, stanza);
XMLFreeElement(stanza);
continue;
@ -770,7 +1145,8 @@ ParseeXMPPThread(void *argp)
XMLElement *stanza = NULL;
XMPPThreadInfo info;
pthread_mutex_t stanzas_lock = PTHREAD_MUTEX_INITIALIZER;
size_t i;
size_t i, j = 0;
/* Initialise the FIFO */
info.stanzas = ArrayCreate();
@ -795,6 +1171,7 @@ ParseeXMPPThread(void *argp)
pthread_create(thr, NULL, XMPPDispatcher, &info.dispatchers[i]);
}
while (true)
{
char *to, *room, *from, *from_matrix;
@ -829,7 +1206,6 @@ ParseeXMPPThread(void *argp)
}
PushStanza(&info, stanza);
//XMLFreeElement(stanza);
}
info.running = false;