mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 15:05:11 +00:00
[MOD/FIX] Separate XMPP thread, fix EINVAL issue
threading is good actually. please hmu if you ever trigger the achievement.
This commit is contained in:
parent
299f473a81
commit
143bdf0a5a
21 changed files with 2314 additions and 1764 deletions
447
src/XMPPThread/Stanzas/IQ.c
Normal file
447
src/XMPPThread/Stanzas/IQ.c
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
#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
|
||||
IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
||||
{
|
||||
XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
base64 = TrimBase64(data->data);
|
||||
b64len = base64 ? strlen(base64) : 0;
|
||||
length = Base64DecodedSize(base64, b64len);
|
||||
|
||||
/* TODO: Bound checks for a size limit. */
|
||||
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 = 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");
|
||||
|
||||
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 = 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,
|
||||
tdata ? tdata->data : NULL
|
||||
);
|
||||
Free(bdata);
|
||||
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))
|
||||
{
|
||||
from_matrix = ParseeEncodeJID(args->config, jid, false);
|
||||
ASSetAvatar(args->config, from_matrix, mxc);
|
||||
}
|
||||
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;
|
||||
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"))
|
||||
{
|
||||
Log(LOG_INFO, "vCard information GET for %s", to);
|
||||
|
||||
/* TODO: "a compliant server MUST respond on behalf of the
|
||||
* requestor and not forward the IQ to the requestee's
|
||||
* connected resource". */
|
||||
if (!strncmp(to, "parsee@", 7))
|
||||
{
|
||||
XMLElement *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);
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
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
|
||||
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;
|
||||
type = HashMapGet(stanza->attrs, "type");
|
||||
#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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue