Parsee/src/XMPPThread/Stanzas/IQ.c

659 lines
19 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 <sys/utsname.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((int) *b64))
{
b64++;
continue;
}
tmp = ret;
ret = StrConcat(2, ret, ch);
Free(tmp);
b64++;
}
return ret;
}
static bool
AvatarGrab(ParseeData *data, char *mxc, char **mime, char **b64, size_t *len)
{
char *mimei = NULL, *outi = NULL, *b64i = NULL;
size_t sizei;
if (!data || !mxc || !mime || !b64 || !len)
{
return false;
}
if (!ASGrab(data->config, mxc, &mimei, &outi, &sizei))
{
Free(mimei);
Free(outi);
return false;
}
b64i = Base64Encode(outi, sizei);
Free(outi);
if (!b64i)
{
Free(mimei);
b64i = StrDuplicate(media_unknown); /* TODO: Different assets! */
mimei = StrDuplicate("image/png");
}
*mime = mimei;
*b64 = b64i;
*len = sizei;
return true;
}
XMLElement *
GenerateAvatarData(ParseeData *data, char *mxid)
{
char *mxc = NULL, *mime = NULL, *b64 = NULL;
XMLElement *elem = NULL, *type, *binval;
size_t len = 0;
if (!data || !mxid)
{
return NULL;
}
/* TODO: Use the right room for the avatar! */
mxc = ASGetAvatar(data->config, NULL, mxid);
if (!mxc || !AvatarGrab(data, mxc, &mime, &b64, &len))
{
goto end;
}
elem = XMLCreateTag("PHOTO");
type = XMLCreateTag("TYPE");
binval = XMLCreateTag("BINVAL");
XMLAddChild(type, XMLCreateText(mime));
XMLAddChild(binval, XMLCreateText(b64));
XMLAddChild(elem, type);
XMLAddChild(elem, binval);
end:
Free(mime);
Free(mxc);
Free(b64);
return elem;
}
#define DISCO "http://jabber.org/protocol/disco#info"
static XMLElement *
IQGenerateQuery(IQFeatures *features)
{
XMLElement *query;
if (!features)
{
return NULL;
}
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO);
{
XMLElement *feature;
size_t i;
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
feature = XMLCreateTag("identity");
XMLAddAttr(feature, "category", identity->category);
XMLAddAttr(feature, "type", identity->type);
XMLAddAttr(feature, "name", identity->name);
XMLAddChild(query, feature);
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
char *var = ArrayGet(features->adverts, i);
feature = XMLCreateTag("feature");
XMLAddAttr(feature, "var", var);
XMLAddChild(query, feature);
}
}
return query;
}
void
IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
{
char *from, *to, *id;
XMLElement *iq_reply, *query;
IQFeatures *features;
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);
features = LookupJIDFeatures(to);
query = IQGenerateQuery(features);
{
char *ver = XMPPGenerateVer(features);
char *node = StrConcat(3, REPOSITORY, "#", ver);
XMLAddAttr(query, "node", node);
Free(node);
Free(ver);
}
XMLAddChild(iq_reply, query);
FreeIQFeatures(features);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
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, *to;
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);
to = HashMapGet(stanza->attrs, "to");
ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host);
Free(parsee);
return ret;
}
static bool
IsInMUC(ParseeData *data, char *jid)
{
char *trimmed = ParseeTrimJID(jid);
char *chat_id = ParseeGetFromMUCID(data, trimmed);
Free(trimmed);
Free(chat_id);
return !!chat_id;
}
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");
char *trimmed = ParseeTrimJID(from);
XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "from", to);
XMLAddAttr(iq_reply, "to", from);
XMLAddAttr(iq_reply, "id", id);
{
XMLElement *q = XMLCreateTag("query");
char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee");
XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items");
XMLAddAttr(q, "node", "http://jabber.org/protocol/commands");
XMPPShoveCommandList(thr->info->m,
IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza
);
XMLAddChild(iq_reply, q);
Free(parsee_muc_jid);
}
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLFreeElement(iq_reply);
Free(trimmed);
}
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 (%s)", to, to_matrix);
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);
}
XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size);
XMLFreeElement(iqVCard);
Free(to_matrix);
Free(name);
return;
}
Free(to_matrix);
to_matrix = ParseeDecodeMXID(to);
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");
{
XMLAddChild(vCard, GenerateAvatarData(args, to_matrix));
Free(mto_link);
}
XMLAddChild(iqVCard, vCard);
}
XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size);
XMLFreeElement(iqVCard);
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.
* NOTE: I explicitely choose to not do any manipulation
* because messing with random user images is inherently a
* risk I do *not* want to take. */
char *to_matrix = ParseeDecodeMXID(to);
char *avatar = ASGetAvatar(args->config, NULL, to_matrix);
char *mime = NULL;
char *b64 = NULL;
size_t len = 0;
XMLElement *reply;
AvatarGrab(args, avatar, &mime, &b64, &len);
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);
}
XMPPSendStanza(jabber, reply, args->config->max_stanza_size);
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, *os;
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");
{
struct utsname info;
name = XMLCreateTag("name");
version = XMLCreateTag("version");
os = XMLCreateTag("os");
uname(&info);
XMLAddChild(name, XMLCreateText(NAME));
XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]"));
XMLAddChild(os, XMLCreateText(info.sysname));
}
XMLAddChild(query, name);
XMLAddChild(query, version);
XMLAddChild(query, os);
XMLAddChild(iq_reply, query);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLFreeElement(iq_reply);
}
else
{
char *buf = NULL;
Stream *s = StrStreamWriter(&buf);
Log(LOG_WARNING, "Unknown I/Q received:");
XMLEncode(s, stanza);
StreamFlush(s);
StreamClose(s);
Log(LOG_WARNING, "%s", buf);
Free(buf);
}
}
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
}