Parsee/src/XMPPThread/Stanzas/IQ.c
LDA 6167732e83 [FIX] BMP for MUC nicks, dip toes in vCard avatars
Can, your, Bifrost, Do, That. -lh
2024-09-21 13:18:59 +02:00

585 lines
18 KiB
C

#include "XMPPThread/internal.h"
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Base64.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <StringStream.h>
#include <Matrix.h>
#include <AS.h>
#include <string.h>
#include <ctype.h>
static XMLElement *
CreateTagWithText(const char *tn, char *text)
{
XMLElement *tag = XMLCreateTag((char *) tn);
XMLElement *tex = XMLCreateText(text);
XMLAddChild(tag, tex);
return tag;
}
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;
}
#define DISCO "http://jabber.org/protocol/disco#info"
static XMLElement *
IQGenerateQuery(void)
{
XMLElement *query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO);
{
XMLElement *feature;
#define IdentitySimple(c,t,n) do \
{ \
feature = XMLCreateTag("identity"); \
XMLAddAttr(feature, "category", c); \
XMLAddAttr(feature, "type", t); \
XMLAddAttr(feature, "name", n); \
XMLAddChild(query, feature); \
} \
while (0);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(f) do \
{ \
feature = XMLCreateTag("feature"); \
XMLAddAttr(feature, "var", f); \
XMLAddChild(query, feature); \
} \
while (0);
IQ_ADVERT
#undef AdvertiseSimple
}
return query;
}
void
IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
{
char *from, *to, *id;
XMLElement *iq_reply, *query;
from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to");
id = HashMapGet(stanza->attrs, "id");
/* Generate an IQ reply with discovery information */
iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "to", from);
XMLAddAttr(iq_reply, "from", to);
XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "id", id);
query = IQGenerateQuery();
{
char *ver = XMPPGenerateVer();
char *node = StrConcat(3, REPOSITORY, "#", ver);
XMLAddAttr(query, "node", node);
Free(node);
Free(ver);
}
XMLAddChild(iq_reply, query);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
(void) args;
}
void
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"
);
(void) thr;
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;
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;
}
JsonValueFree(JsonSet(
json, JsonValueString(id),
1, from
));
DbUnlock(args->db, avatars);
base64 = TrimBase64(data->data);
b64len = base64 ? strlen(base64) : 0;
length = Base64DecodedSize(base64, b64len);
bdata = (char *) Base64Decode(base64, b64len);
datastream = StrStreamReaderN(bdata, length);
/* XEP-0084 effectively assumes an image/png MIME. */
mxc = ASUpload(args->config, datastream, length, "image/png");
jid = ParseeLookupJID(from);
from_matrix = ParseeGetBridgedUser(args, stanza);
Log(LOG_DEBUG,
"Setting the avatar of '%s' to %s",
from_matrix, mxc
);
ASSetAvatar(args->config, from_matrix, mxc);
Free(mxc);
Free(jid);
Free(bdata);
Free(from_matrix);
Free(base64);
StreamClose(datastream);
}
}
}
if (vcard)
{
XMLElement *photo = XMLookForUnique(vcard, "PHOTO");
if (photo)
{
XMLElement *binval = XMLookForUnique(photo, "BINVAL");
XMLElement *type = XMLookForUnique(photo, "TYPE");
XMLElement *data =
binval ? ArrayGet(binval->children, 0) : NULL;
XMLElement *tdata = type ? ArrayGet(type->children, 0) : NULL;
char *base64;
char *bdata;
size_t length, b64len;
Stream *datastream;
char *mxc = NULL, *from_matrix = NULL, *jid = NULL;
char *room = NULL, *resource;
char *from = HashMapGet(stanza->attrs, "from");
if (!data || !data->data)
{
Log(LOG_ERR, "%s NOT FOUND", HashMapGet(stanza->attrs, "from"));
return;
}
/* Get the base64, decode it, and shove it in a nicely put
* MXC address */
base64 = TrimBase64(data->data);
b64len = base64 ? strlen(base64) : 0;
length = Base64DecodedSize(base64, b64len);
bdata = Base64Decode(base64, b64len);
datastream = StrStreamReaderN(bdata, length);
Log(LOG_DEBUG, "(%dB @ %p) = %p (%d)", b64len, base64, bdata, length);
mxc = ASUpload(
args->config,
datastream,
length,
tdata ? tdata->data : NULL
);
Free(bdata);
Free(base64);
StreamClose(datastream);
room = ParseeGetBridgedRoom(args, stanza);
jid = ParseeLookupJID(HashMapGet(stanza->attrs, "from"));
resource = ParseeTrimJID(from);
/* TODO: More reliable system for telling the difference appart */
if (jid && !StrEquals(from, resource))
{
char *oid = ParseeLookupOID(jid);
char *hsh = ParseeSHA256(oid);
bool scrambled =
oid && StrEquals(jid, HashMapGet(stanza->attrs, "from"));
from_matrix = scrambled
? ScrambleOID(args, oid)
: ParseeGetBridgedUser(args, stanza);
Log(LOG_DEBUG,
"Setting the vCard of '%s'(%s) to %s (%d B)",
from_matrix, jid, mxc,
length
);
Log(LOG_DEBUG,
"OID=%s[%s]",
oid, hsh
);
ASSetAvatar(args->config, from_matrix, mxc);
Free(oid);
Free(hsh);
}
else if (room)
{
char *mask = ParseeMXID(args);
HashMap *obj = HashMapCreate();
HashMapSet(obj, "url", JsonValueString(mxc));
ASSetState(
args->config,
room, "m.room.avatar", "",
mask, obj
);
Free(mask);
}
Free(from_matrix);
Free(resource);
Free(room);
Free(jid);
Free(mxc);
}
}
}
bool
IQIsCommandList(ParseeData *args, XMLElement *stanza)
{
char *parsee = NULL;
XMLElement *query = XMLookForTKV(
stanza, "query", "xmlns",
"http://jabber.org/protocol/disco#items"
);
bool ret = false;
if (!query ||
!StrEquals(HashMapGet(query->attrs, "node"),
"http://jabber.org/protocol/commands"))
{
return false;
}
parsee = ParseeJID(args);
ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee);
Free(parsee);
return ret;
}
void
IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
XMPPComponent *jabber = args->jabber;
XMLElement *pubsub;
char *from = HashMapGet(stanza->attrs, "from");
char *to = HashMapGet(stanza->attrs, "to");
char *id = HashMapGet(stanza->attrs, "id");
(void) thr;
if (IQIsCommandList(args, stanza))
{
XMLElement *iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "from", to);
XMLAddAttr(iq_reply, "to", from);
XMLAddAttr(iq_reply, "id", id);
{
XMLElement *q = XMLCreateTag("query");
XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items");
XMLAddAttr(q, "node", "http://jabber.org/protocol/commands");
XMPPShoveCommandList(thr->info->m, to, q);
XMLAddChild(iq_reply, q);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
}
else if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp"))
{
char *to_matrix = ParseeGetBridgedUser(args, stanza);
char *name = ASGetName(args->config, NULL, to_matrix);
XMLElement *iqVCard;
Log(LOG_DEBUG, "vCard information GET for %s", to);
if (!strncmp(to, "parsee@", 7))
{
iqVCard = XMLCreateTag("iq");
XMLAddAttr(iqVCard, "from", to);
XMLAddAttr(iqVCard, "to", from);
XMLAddAttr(iqVCard, "id", id);
XMLAddAttr(iqVCard, "type", "result");
{
XMLElement *vCard = XMLCreateTag("vCard");
XMLAddAttr(vCard, "xmlns", "vcard-temp");
{
XMLElement *fn = CreateTagWithText(
"FN", "Parsee Mizuhashi"
);
XMLElement *nick = CreateTagWithText(
"NICKNAME", "Parsee"
);
XMLElement *url = CreateTagWithText(
"URL", REPOSITORY
);
/* TODO: Maybe abstract the vCard code. */
XMLAddChild(vCard, nick);
XMLAddChild(vCard, url);
XMLAddChild(vCard, fn);
}
XMLAddChild(iqVCard, vCard);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iqVCard);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iqVCard);
Free(to_matrix);
Free(name);
return;
}
iqVCard = XMLCreateTag("iq");
XMLAddAttr(iqVCard, "from", to);
XMLAddAttr(iqVCard, "to", from);
XMLAddAttr(iqVCard, "id", id);
XMLAddAttr(iqVCard, "type", "result");
{
XMLElement *vCard = XMLCreateTag("vCard");
char *mto_link = ParseeGenerateMTO(to_matrix);
XMLAddAttr(vCard, "xmlns", "vcard-temp");
{
XMLElement *fn = CreateTagWithText(
"FN", name ? name : to_matrix
);
XMLElement *nick = CreateTagWithText(
"NICKNAME", to_matrix
);
XMLElement *url = CreateTagWithText(
"URL", mto_link
);
/* TODO: Maybe abstract the vCard code. */
/* TODO: Make a function to just get a user's avatar
* automatically. */
XMLAddChild(vCard, nick);
XMLAddChild(vCard, url);
XMLAddChild(vCard, fn);
Free(mto_link);
}
XMLAddChild(iqVCard, vCard);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iqVCard);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
Free(to_matrix);
Free(name);
}
#define PS "http://jabber.org/protocol/pubsub"
else if ((pubsub = XMLookForTKV(stanza, "pubsub", "xmlns", PS)))
{
/* TODO: Pass this through the PEP manager */
XMLElement *a_items = XMLookForTKV(pubsub,
"items", "node", "urn:xmpp:avatar:data"
);
if (a_items)
{
/* Do, without regret, start shoving an avatar out the bus */
char *to_matrix = ParseeDecodeMXID(to);
char *avatar = ASGetAvatar(args->config, NULL, to_matrix);
char *buf, *mime;
char *b64;
size_t len;
XMLElement *reply;
ASGrab(args->config, avatar, &mime, &buf, &len);
b64 = Base64Encode(buf, len);
Free(buf);
Log(LOG_DEBUG, "IQ-GET: PUBSUB AVATAR OF=%s", to_matrix);
/* Strike back with a response */
reply = XMLCreateTag("iq");
XMLAddAttr(reply, "type", "result");
XMLAddAttr(reply, "to", from);
XMLAddAttr(reply, "from", to);
XMLAddAttr(reply, "id", HashMapGet(stanza->attrs, "id"));
{
XMLElement *ps = XMLCreateTag("pubsub");
XMLElement *items = XMLCreateTag("items");
XMLAddAttr(ps, "xmlns", PS);
XMLAddAttr(items, "node", "urn:xmpp:avatar:data");
{
XMLElement *item = XMLCreateTag("item");
XMLElement *data = XMLCreateTag("data");
XMLAddAttr(item, "id", "TODO");
XMLAddAttr(data, "xmlns", "urn:xmpp:avatar:data");
XMLAddChild(data, XMLCreateText(b64));
XMLAddChild(item, data);
XMLAddChild(items, item);
}
XMLAddChild(ps, items);
XMLAddChild(reply, ps);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(reply);
Free(to_matrix);
Free(avatar);
Free(mime);
Free(b64);
}
}
#undef PS
else if (XMLookForTKV(stanza, "query", "xmlns", DISCO))
{
IQDiscoGet(args, jabber, stanza);
}
else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version"))
{
XMLElement *iq_reply, *query;
XMLElement *name, *version;
iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "to", from);
XMLAddAttr(iq_reply, "from", to);
XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "id", id);
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "jabber:iq:version");
{
name = XMLCreateTag("name");
version = XMLCreateTag("version");
XMLAddChild(name, XMLCreateText(NAME));
XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]"));
}
XMLAddChild(query, name);
XMLAddChild(query, version);
XMLAddChild(iq_reply, query);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
}
else
{
Log(LOG_WARNING, "Unknown I/Q received:");
XMLEncode(StreamStdout(), stanza);
StreamPrintf(StreamStdout(),"\n");
StreamFlush(StreamStdout());
}
}
void
IQError(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
/* TODO */
(void) args;
(void) stanza;
(void) thr;
}
void
IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
XMPPCommandManager *manager = thr->info->m;
XMPPManageCommand(manager, stanza, args);
}
#undef DISCO
void
IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
char *type = HashMapGet(stanza->attrs, "type");
if (PEPManagerHandle(thr->info->pep_manager, stanza))
{
return;
}
#define OnType(ctyp, callback) do \
{ \
if (StrEquals(type, #ctyp)) \
{ \
callback(args, stanza, thr); \
return; \
} \
} \
while (0)
OnType(get, IQGet);
OnType(set, IQSet);
OnType(error, IQError);
OnType(result, IQResult);
#undef OnType
}