mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 13:45:10 +00:00
585 lines
18 KiB
C
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
|
|
}
|