mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 15:05:11 +00:00
588 lines
18 KiB
C
588 lines
18 KiB
C
#include "XMPPThread/internal.h"
|
|
|
|
#include <Cytoplasm/Memory.h>
|
|
#include <Cytoplasm/Util.h>
|
|
#include <Cytoplasm/Str.h>
|
|
#include <Cytoplasm/Log.h>
|
|
|
|
#include <Matrix.h>
|
|
#include <AS.h>
|
|
|
|
#include <string.h>
|
|
|
|
static void
|
|
LazyRegister(ParseeData *data, char *mxid, char *name)
|
|
{
|
|
DbRef *ref;
|
|
char *hash = ParseeHMACS(data->id, mxid);
|
|
char *dbname;
|
|
if (!(ref = DbLock(data->db, 2, "users", hash)))
|
|
{
|
|
ASRegisterUser(data->config, mxid);
|
|
ref = DbCreate(data->db, 2, "users", hash);
|
|
if (ref)
|
|
{
|
|
HashMapSet(DbJson(ref), "mxid", JsonValueString(mxid));
|
|
HashMapSet(DbJson(ref),
|
|
"ts", JsonValueInteger(UtilTsMillis())
|
|
);
|
|
}
|
|
}
|
|
dbname = GrabString(DbJson(ref), 1, "name");
|
|
if (name && !StrEquals(dbname, name))
|
|
{
|
|
ASSetName(data->config, mxid, name);
|
|
if (ref)
|
|
{
|
|
JsonValueFree(HashMapSet(
|
|
DbJson(ref), "name", JsonValueString(name)
|
|
));
|
|
}
|
|
}
|
|
DbUnlock(data->db, ref);
|
|
Free(hash);
|
|
}
|
|
static char *
|
|
LazySend(ParseeData *args, char *mxid, char *mroom_id, char *type, HashMap *ev)
|
|
{
|
|
HashMap *duplicate;
|
|
char *event;
|
|
if (!args || !mxid || !mroom_id || !ev)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (!type)
|
|
{
|
|
type = "m.room.message";
|
|
}
|
|
|
|
duplicate = JsonDuplicate(ev);
|
|
event = ASSend(
|
|
args->config, mroom_id, mxid,
|
|
type,
|
|
ev
|
|
);
|
|
if (event)
|
|
{
|
|
JsonFree(duplicate);
|
|
return event;
|
|
}
|
|
|
|
ASInvite(args->config, mroom_id, mxid);
|
|
Free(ASJoin(args->config, mroom_id, mxid));
|
|
|
|
return ASSend(
|
|
args->config, mroom_id, mxid,
|
|
type, duplicate
|
|
);
|
|
}
|
|
|
|
static void
|
|
ProcessChatstates(ParseeData *args, XMLElement *stanza)
|
|
{
|
|
char *from_matrix, *mroom_id;
|
|
#define CHAT_STATES "http://jabber.org/protocol/chatstates"
|
|
if (XMLookForTKV(stanza, "composing", "xmlns", CHAT_STATES))
|
|
{
|
|
from_matrix = ParseeGetBridgedUser(args, stanza);
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
|
|
ASType(args->config, from_matrix, mroom_id, true);
|
|
|
|
Free(from_matrix);
|
|
Free(mroom_id);
|
|
mroom_id = NULL;
|
|
from_matrix = NULL;
|
|
}
|
|
if (XMLookForTKV(stanza, "active", "xmlns", CHAT_STATES))
|
|
{
|
|
char *latest = NULL;
|
|
from_matrix = ParseeGetBridgedUser(args, stanza);
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
|
|
latest = ParseeLookupHead(mroom_id);
|
|
|
|
ASType(args->config, from_matrix, mroom_id, false);
|
|
ASPresence(args->config, from_matrix, mroom_id, latest);
|
|
|
|
Free(from_matrix);
|
|
Free(latest);
|
|
Free(mroom_id);
|
|
mroom_id = NULL;
|
|
from_matrix = NULL;
|
|
}
|
|
if (XMLookForTKV(stanza, "paused", "xmlns", CHAT_STATES) ||
|
|
XMLookForTKV(stanza, "inactive", "xmlns", CHAT_STATES))
|
|
{
|
|
char *latest = NULL;
|
|
from_matrix = ParseeGetBridgedUser(args, stanza);
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
|
|
latest = ParseeLookupHead(mroom_id);
|
|
|
|
ASType(args->config, from_matrix, mroom_id, false);
|
|
ASPresence(args->config, from_matrix, mroom_id, latest);
|
|
|
|
Free(from_matrix);
|
|
Free(latest);
|
|
Free(mroom_id);
|
|
mroom_id = NULL;
|
|
from_matrix = NULL;
|
|
}
|
|
if (XMLookForTKV(stanza, "received", "xmlns", "urn:xmpp:receipts") ||
|
|
XMLookForTKV(stanza, "displayed", "xmlns", "urn:xmpp:chat-markers:0"))
|
|
{
|
|
/* TODO: Use stanza ID if possible */
|
|
char *latest = NULL;
|
|
from_matrix = ParseeGetBridgedUser(args, stanza);
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
|
|
latest = ParseeLookupHead(mroom_id);
|
|
|
|
ASPresence(args->config, from_matrix, mroom_id, latest);
|
|
|
|
Free(from_matrix);
|
|
Free(latest);
|
|
Free(mroom_id);
|
|
mroom_id = NULL;
|
|
from_matrix = NULL;
|
|
|
|
}
|
|
#undef CHAT_STATES
|
|
}
|
|
|
|
static float
|
|
TimeElapsed(uint64_t *rectime, uint64_t v)
|
|
{
|
|
uint64_t time = UtilTsMillis();
|
|
float t = ((time-v)/1000.f);
|
|
*rectime = time;
|
|
|
|
return t;
|
|
}
|
|
bool
|
|
MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|
{
|
|
XMPPComponent *jabber = args->jabber;
|
|
|
|
XMLElement *reactions = NULL;
|
|
XMLElement *body = NULL;
|
|
XMLElement *data = NULL;
|
|
|
|
char *to, *room, *from, *from_matrix, *decode_from;
|
|
char *mroom_id = NULL;
|
|
char *replaced = XMPPGetReplacedID(stanza);
|
|
char *retracted = XMPPGetRetractedID(stanza);
|
|
char *reply_to = XMPPGetReply(stanza);
|
|
char *moderated = XMPPGetModeration(stanza);
|
|
char *type = HashMapGet(stanza->attrs, "type");
|
|
bool chat = StrEquals(type, "chat");
|
|
size_t i;
|
|
uint64_t time, rectime;
|
|
#define Elapsed(v) (TimeElapsed(&rectime, v))
|
|
|
|
|
|
to = NULL;
|
|
from = NULL;
|
|
decode_from = NULL;
|
|
from_matrix = NULL;
|
|
Log(LOG_DEBUG, "<message> usage=%d", MemoryAllocated());
|
|
time = UtilTsMillis();
|
|
rectime = time;
|
|
|
|
from = HashMapGet(stanza->attrs, "from");
|
|
if (ParseeManageBan(args, from, NULL))
|
|
{
|
|
XMLFreeElement(stanza);
|
|
Log(LOG_DEBUG,
|
|
"<message/> time=%f "
|
|
"usage=%d (%s:%d)",
|
|
Elapsed(time),
|
|
MemoryAllocated(), __FILE__, __LINE__
|
|
);
|
|
return false;
|
|
}
|
|
|
|
|
|
if (ServerHasXEP421(args, from))
|
|
{
|
|
XMLElement *occupant = XMLookForTKV(
|
|
stanza, "occupant-id",
|
|
"xmlns", "urn:xmpp:occupant-id:0"
|
|
);
|
|
char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL;
|
|
if (occ_id)
|
|
{
|
|
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
|
|
{
|
|
Log(LOG_DEBUG,
|
|
"'%s' has support for XEP-421, fetching OID=%s",
|
|
from, occ_id
|
|
);
|
|
}
|
|
ParseePushOIDTable(from, occ_id);
|
|
}
|
|
}
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "XEP-421: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
if (StrEquals(type, "error"))
|
|
{
|
|
char *type, *text, *user, *parsee;
|
|
char *message, *room;
|
|
|
|
from = HashMapGet(stanza->attrs, "from");
|
|
to = HashMapGet(stanza->attrs, "to");
|
|
|
|
/* Tiny little trick. */
|
|
XMLAddAttr(stanza, "type", "groupchat");
|
|
|
|
parsee = ParseeMXID(args);
|
|
type = XMPPGetErrtype(stanza);
|
|
text = XMPPGetErrtext(stanza);
|
|
user = ParseeDecodeMXID(to);
|
|
|
|
/* Avoid fun hypothetical cases of looping. */
|
|
if (StrEquals(parsee, user))
|
|
{
|
|
goto end_error;
|
|
}
|
|
|
|
message = StrConcat(3, type, ": ", text);
|
|
room = ParseeGetBridgedRoom(args, stanza);
|
|
Free(LazySend(
|
|
args, parsee, room, NULL,
|
|
MatrixCreateNotice(message)
|
|
));
|
|
|
|
end_error:
|
|
XMLFreeElement(stanza);
|
|
Free(message);
|
|
Free(parsee);
|
|
Free(room);
|
|
Free(user);
|
|
Log(LOG_DEBUG,
|
|
"<message/> time=%f "
|
|
"usage=%d (%s:%d)",
|
|
Elapsed(time),
|
|
MemoryAllocated(), __FILE__, __LINE__
|
|
);
|
|
return false;
|
|
}
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
if (moderated)
|
|
{
|
|
/* TODO: Parsee MUST check if it is a valid MUC */
|
|
char *resource = ParseeGetResource(from);
|
|
char *chat_id = ParseeGetFromMUCID(args, from);
|
|
char *event_id = NULL;
|
|
char *encoded = ParseeMXID(args);
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
|
|
if (!resource && chat_id)
|
|
{
|
|
event_id = ParseeGetEventFromID(args, stanza, moderated);
|
|
ASRedact(args->config, mroom_id, NULL, event_id);
|
|
ParseePushAllStanza(args, stanza, event_id);
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
|
|
Free(event_id);
|
|
}
|
|
Free(mroom_id);
|
|
Free(resource);
|
|
Free(encoded);
|
|
Free(chat_id);
|
|
}
|
|
body = XMLookForUnique(stanza, "body");
|
|
|
|
PEPManagerHandle(thr->info->pep_manager, stanza);
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "PEP management: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
|
|
decode_from = ParseeLookupJID(from);
|
|
from_matrix = ParseeEncodeJID(args->config, decode_from, true);
|
|
room = ParseeFindDMRoom(args, to, from);
|
|
data = body ? ArrayGet(body->children, 0) : NULL;
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Fetching user info: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
/* TODO: CLEAN THAT UP INTO A CREATEDM FUNCTION */
|
|
mroom_id = ParseeGetBridgedRoom(args, stanza);
|
|
Log(LOG_DEBUG, "Bridging event to '%s'...", mroom_id);
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime));
|
|
}
|
|
if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) &&
|
|
to && *to == '@')
|
|
{
|
|
DbRef *room_ref;
|
|
HashMap *room_json;
|
|
char *from = HashMapGet(stanza->attrs, "from");
|
|
|
|
LazyRegister(args, from_matrix, NULL);
|
|
room = ASCreateDM(args->config, from_matrix, to);
|
|
mroom_id = StrDuplicate(room);
|
|
if (room)
|
|
{
|
|
room_ref = DbCreate(args->db, 3, "rooms", room, "data");
|
|
room_json = DbJson(room_ref);
|
|
HashMapSet(room_json, "is_direct", JsonValueBoolean(true));
|
|
HashMapSet(room_json, "xmpp_user", JsonValueString(from));
|
|
DbUnlock(args->db, room_ref);
|
|
ParseePushDMRoom(args, to, from, room);
|
|
}
|
|
}
|
|
|
|
/* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER
|
|
* SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED
|
|
* ISSUE.
|
|
*
|
|
* I HATE THIS. I NEED TO FIND A BETTER WAY. */
|
|
if (!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
if (mroom_id && !XMPPIsParseeStanza(stanza))
|
|
{
|
|
char *res = ParseeGetResource(from);
|
|
char *encoded = ParseeGetBridgedUser(args, stanza);
|
|
char *event_id = NULL;
|
|
char *parsee = ParseeJID(args);
|
|
char *trim = ParseeTrimJID(from);
|
|
XMLElement *ps;
|
|
|
|
/* Subscribe to the sender's metadata node. */
|
|
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);
|
|
|
|
ps = CreatePubsubRequest(
|
|
parsee, trim, "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);
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Subscription to XEP-0084: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
Free(parsee);
|
|
Free(trim);
|
|
|
|
pthread_mutex_lock(&thr->info->chk_lock);
|
|
if (ParseeVerifyAllStanza(args, stanza) && !replaced)
|
|
{
|
|
XMLElement *oob, *oob_data;
|
|
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
|
|
LazyRegister(args, encoded, !chat ? res : NULL);
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Matrix registration: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
reactions = XMLookForTKV(stanza,
|
|
"reactions", "xmlns", "urn:xmpp:reactions:0"
|
|
);
|
|
|
|
/* Check if it is a media link */
|
|
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
|
|
if (oob && data)
|
|
{
|
|
char *mxc, *mime = NULL;
|
|
HashMap *content = NULL;
|
|
oob_data = XMLookForUnique(oob, "url");
|
|
oob_data =
|
|
oob_data ? ArrayGet(oob_data->children, 0) : NULL;
|
|
|
|
if (oob_data)
|
|
{
|
|
FileInfo *info = FileInfoFromXMPP(stanza);
|
|
mxc = ASReupload(args->config, oob_data->data, &mime);
|
|
if (mxc)
|
|
{
|
|
content = MatrixCreateMedia(
|
|
mxc, data->data, mime, info
|
|
);
|
|
|
|
HashMapSet(content,
|
|
"at.kappach.at.parsee.external",
|
|
JsonValueString(oob_data->data)
|
|
);
|
|
ShoveStanza(content, stanza);
|
|
|
|
event_id = LazySend(
|
|
args, encoded, mroom_id, NULL,
|
|
content
|
|
);
|
|
Free(mxc);
|
|
}
|
|
FileInfoFree(info);
|
|
Free(mime);
|
|
}
|
|
}
|
|
else if (reactions)
|
|
{
|
|
Array *react_child = reactions->children;
|
|
Array *to_redact;
|
|
size_t reacts = ArraySize(react_child);
|
|
event_id = ParseeGetReactedEvent(args, stanza);
|
|
|
|
/* Redact the last few reactions. */
|
|
to_redact = ASGetRelations(
|
|
args->config, 30, mroom_id, event_id,
|
|
"m.reaction"
|
|
);
|
|
for (i = 0; i < ArraySize(to_redact); i++)
|
|
{
|
|
HashMap *e = ArrayGet(to_redact, i);
|
|
char *e_sender = GrabString(e, 1, "sender");
|
|
char *e_id = GrabString(e, 1, "event_id");
|
|
if (!StrEquals(e_sender, encoded))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ASRedact(args->config, mroom_id, encoded, e_id);
|
|
}
|
|
ASFreeRelations(to_redact);
|
|
|
|
/* ... and push out the new ones. */
|
|
for (i = 0; i < reacts; i++)
|
|
{
|
|
XMLElement *reaction, *react_data;
|
|
reaction = ArrayGet(react_child, i);
|
|
react_data = ArrayGet(reaction->children, 0);
|
|
|
|
event_id = LazySend(
|
|
args, encoded, mroom_id, "m.reaction",
|
|
ShoveStanza(
|
|
MatrixCreateReact(event_id, react_data->data),
|
|
stanza
|
|
)
|
|
);
|
|
}
|
|
Free(event_id);
|
|
event_id = NULL;
|
|
}
|
|
else if (retracted)
|
|
{
|
|
event_id = ParseeGetEventFromID(args, stanza, retracted);
|
|
ASRedact(args->config, mroom_id, encoded, event_id);
|
|
ParseePushAllStanza(args, stanza, event_id);
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
|
|
Free(event_id);
|
|
event_id = NULL;
|
|
}
|
|
else if (data)
|
|
{
|
|
/* TODO: Respect the fallback trims the stanza provides us
|
|
* if possible. Element does not like raw bodies on replies
|
|
* too. Go figure. */
|
|
size_t off =
|
|
reply_to ? ParseeFindDatastart(data->data) : 0;
|
|
HashMap *ev = MatrixCreateMessage(data->data + off);
|
|
if (reply_to)
|
|
{
|
|
char *reply_id = ParseeGetEventFromID(args, stanza, reply_to);
|
|
MatrixSetReply(ev, reply_id);
|
|
Free(reply_id);
|
|
}
|
|
ShoveStanza(ev, stanza);
|
|
event_id = LazySend(
|
|
args, encoded, mroom_id, NULL,
|
|
ev
|
|
);
|
|
}
|
|
pthread_mutex_lock(&thr->info->chk_lock);
|
|
ParseePushAllStanza(args, stanza, event_id);
|
|
Free(event_id);
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
}
|
|
else if (replaced && data)
|
|
{
|
|
/* TODO: Unify these. Maybe even make a function
|
|
* that objectifies a stanza. */
|
|
size_t off =
|
|
reply_to ? ParseeFindDatastart(data->data) : 0;
|
|
event_id = ParseeGetEventFromID(args, stanza, replaced);
|
|
HashMap *ev = ShoveStanza(
|
|
MatrixCreateReplace(event_id, data->data + off),
|
|
stanza
|
|
);
|
|
|
|
Log(LOG_DEBUG, "Replacing events in %s(%s)", mroom_id, event_id);
|
|
Free(LazySend(
|
|
args, encoded, mroom_id, NULL,
|
|
ev
|
|
));
|
|
ParseePushAllStanza(args, stanza, event_id);
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
|
|
Free(event_id);
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_unlock(&thr->info->chk_lock);
|
|
}
|
|
Free(res);
|
|
Free(encoded);
|
|
}
|
|
else
|
|
{
|
|
XMLElement *parsee = XMLookForUnique(stanza, "x-parsee");
|
|
XMLElement *event = XMLookForUnique(parsee, "event-id");
|
|
XMLElement *e_d = ArrayGet(event ? event->children : NULL, 0);
|
|
|
|
if (e_d)
|
|
{
|
|
ParseePushAllStanza(args, stanza, e_d->data);
|
|
}
|
|
}
|
|
|
|
ProcessChatstates(args, stanza);
|
|
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
|
|
{
|
|
Log(LOG_DEBUG, "Chatstate management: %fs", Elapsed(rectime));
|
|
}
|
|
|
|
end:
|
|
Free(mroom_id);
|
|
mroom_id = NULL;
|
|
Free(from_matrix);
|
|
Free(decode_from);
|
|
Free(room);
|
|
Free(to);
|
|
Log(LOG_DEBUG,
|
|
"<message/> time=%f "
|
|
"usage=%d (%s:%d)",
|
|
(UtilTsMillis()-time)/1000.f,
|
|
MemoryAllocated(), __FILE__, __LINE__
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|