[ADD/WIP] Basic XEP-0393 support.

Still needs lots of work. Did I fail to mention I _hate_ HTML?
This commit is contained in:
LDA 2024-06-24 18:26:08 +02:00
commit 771c3271ad
10 changed files with 473 additions and 9 deletions

View file

@ -10,6 +10,7 @@
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include <StringStream.h>
#include <Parsee.h> #include <Parsee.h>
#include <XMPP.h> #include <XMPP.h>
#include <XML.h> #include <XML.h>
@ -29,6 +30,7 @@ Main(void)
Stream *yaml; Stream *yaml;
Cron *cron = NULL; Cron *cron = NULL;
memset(&conf, 0, sizeof(conf));
Log(LOG_INFO, Log(LOG_INFO,
"%s - v%s (Cytoplasm %s)", "%s - v%s (Cytoplasm %s)",
NAME, VERSION, CytoplasmGetVersionStr() NAME, VERSION, CytoplasmGetVersionStr()
@ -66,7 +68,6 @@ Main(void)
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart); ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
memset(&conf, 0, sizeof(conf));
conf.port = parsee_conf->port; conf.port = parsee_conf->port;
conf.threads = 4; conf.threads = 4;
conf.maxConnections = 32; conf.maxConnections = 32;

View file

@ -73,12 +73,14 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
char *chat_id, *muc_id, *jid; char *chat_id, *muc_id, *jid;
char *reply_id = MatrixGetReply(event); char *reply_id = MatrixGetReply(event);
char *xepd = ParseeXMPPify(event);
bool direct = false; bool direct = false;
if (ParseeIsPuppet(data->config, sender)) if (ParseeIsPuppet(data->config, sender))
{ {
Free(reply_id); Free(reply_id);
Free(xepd);
return; return;
} }
@ -101,6 +103,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
Free(local); Free(local);
Free(reply_id); Free(reply_id);
Free(xepd);
return; return;
} }
@ -110,6 +113,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
if (!chat_id) if (!chat_id)
{ {
Free(reply_id); Free(reply_id);
Free(xepd);
return; return;
} }
jid = ParseeEncodeMXID(sender); jid = ParseeEncodeMXID(sender);
@ -125,7 +129,11 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
Log(LOG_INFO, "Replying to %s by %s", stanza, sender); Log(LOG_INFO, "Replying to %s by %s", stanza, sender);
} }
XMPPJoinMUC(jabber, jid, rev); XMPPJoinMUC(jabber, jid, rev);
XMPPSendPlain(jabber, jid, muc_id, body, "groupchat", stanza, sender); XMPPSendPlain(
jabber, jid, muc_id,
xepd ? xepd : body, "groupchat",
stanza, sender
);
Free(rev); Free(rev);
Free(name); Free(name);
Free(stanza); Free(stanza);
@ -135,6 +143,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
Free(muc_id); Free(muc_id);
Free(jid); Free(jid);
Free(reply_id); Free(reply_id);
Free(xepd);
} }
void void

View file

@ -132,3 +132,156 @@ ParseeFindDatastart(char *data)
return (int) (startline - data); return (int) (startline - data);
} }
#include <StringStream.h>
static char *
XMPPifyElement(XMLElement *elem)
{
char *xepd = NULL, *tmp = NULL;
size_t i;
XMLElement *child;
char *subxep;
#define Concat(strp) tmp = xepd; \
xepd = StrConcat(2, xepd, strp); \
Free(tmp)
switch (elem->type)
{
case XML_ELEMENT_DATA:
Concat(elem->data);
break;
case XML_ELEMENT_TAG:
if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong"))
{
Concat("*");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
Concat("*");
}
else if (StrEquals(elem->name, "em"))
{
Concat("_");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
Concat("_");
}
else if (StrEquals(elem->name, "code"))
{
Concat("`");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
Concat("`");
}
else if (StrEquals(elem->name, "blockquote"))
{
Concat(">");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
Concat("\n");
}
else if (StrEquals(elem->name, "br"))
{
Concat("\n");
/* HTML fucking SUCKS */
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
}
else if (StrEquals(elem->name, "a"))
{
char *href = HashMapGet(elem->attrs, "href");
Concat("(");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
Concat(" points to ");
Concat(href);
Concat(" )");
}
else
{
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(child);
Concat(subxep);
Free(subxep);
}
}
break;
}
return xepd;
}
char *
ParseeXMPPify(HashMap *event)
{
char *cntr, *type, *format, *html;
char *xepd = NULL, *tmp;
XMLElement *elem;
if (!event)
{
return NULL;
}
/* Check if it is a message event. */
type = JsonValueAsString(HashMapGet(event, "type"));
if (!StrEquals(type, "m.room.message"))
{
return NULL;
}
format = JsonValueAsString(JsonGet(event, 2, "content", "format"));
if (!StrEquals(format, "org.matrix.custom.html"))
{
/* Settle for the raw body instead. */
char *body = JsonValueAsString(JsonGet(event, 2, "content", "body"));
return StrDuplicate(body);
}
html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body"));
html = StrConcat(3, "<html>", html, "</html>");
elem = XMLDecode(StrStreamReader(html), true);
xepd = XMPPifyElement(elem);
XMLFreeElement(elem);
Free(html);
return xepd;
}

131
src/Streams/Reader.c Normal file
View file

@ -0,0 +1,131 @@
#include <StringStream.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Io.h>
#include <string.h>
typedef struct ReaderCookie {
size_t length;
size_t offset;
char *buffer;
} ReaderCookie;
#define Remaining() (cook->length - cook->offset)
static ssize_t
ReadStreamReader(void *coop, void *to, size_t n)
{
ReaderCookie *cook = coop;
size_t remaining;
if (!cook)
{
return 0;
}
remaining = Remaining();
if (n > remaining)
{
memcpy(to, cook->buffer + cook->offset, remaining);
cook->offset = cook->length;
return remaining;
}
memcpy(to, cook->buffer + cook->offset, n);
cook->offset += n;
return n;
}
static ssize_t
WriteStreamReader(void *coop, void *from, size_t n)
{
/* Writing to a stream reader is silly. */
return 0;
}
static off_t
SeekStreamReader(void *coop, off_t mag, int sgn)
{
ReaderCookie *cook = coop;
if (!cook)
{
return 0;
}
switch (sgn)
{
case SEEK_SET:
if (mag > cook->length)
{
cook->offset = cook->length;
return 0;
}
else if (mag < 0)
{
cook->offset = 0;
return 0;
}
cook->offset = mag;
return 0;
case SEEK_CUR:
cook->offset += mag;
if (cook->offset > cook->length)
{
cook->offset = cook->length;
}
else if (cook->offset < 0)
{
cook->offset = 0;
}
return 0;
case SEEK_END:
cook->offset += cook->length + mag;
if (cook->offset > cook->length)
{
cook->offset = cook->length;
}
else if (cook->offset < 0)
{
cook->offset = 0;
}
return 0;
}
return 0;
}
static int
CloseStreamReader(void *coop)
{
/* Nothing to free as of now. */
if (coop)
{
Free(coop);
}
return 0;
}
const static IoFunctions Functions = {
.read = ReadStreamReader,
.seek = SeekStreamReader,
.write = WriteStreamReader,
.close = CloseStreamReader,
};
Stream *
StrStreamReader(char *buffer)
{
Io *raw_io;
ReaderCookie *cookie;
if (!buffer)
{
return NULL;
}
cookie = Malloc(sizeof(*cookie));
cookie->buffer = buffer;
cookie->length = strlen(buffer);
cookie->offset = 0;
raw_io = IoCreate(cookie, Functions);
return StreamIo(raw_io);
}

64
src/Streams/Writer.c Normal file
View file

@ -0,0 +1,64 @@
#include <StringStream.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Io.h>
#include <string.h>
static ssize_t
ReadStreamWriter(void *coop, void *to, size_t n)
{
/* Reading from a stream writer is silly. */
return 0;
}
static ssize_t
WriteStreamWriter(void *coop, void *from, size_t n)
{
char **cook = coop;
char *tmp = NULL;
char *str = Malloc(n + 1);
memcpy(str, from, n);
str[n] = '\0';
tmp = *cook;
*cook = StrConcat(2, *cook, str);
Free(tmp);
Free(str);
return 0;
}
static off_t
SeekStreamWriter(void *coop, off_t mag, int sgn)
{
/* TODO: Seeking would be useful, though not supported yet. */
return 0;
}
static int
CloseStreamWriter(void *coop)
{
/* Nothing to free as of now. */
return 0;
}
const static IoFunctions Functions = {
.read = ReadStreamWriter,
.seek = SeekStreamWriter,
.write = WriteStreamWriter,
.close = CloseStreamWriter,
};
Stream *
StrStreamWriter(char **buffer)
{
Io *raw_io;
if (!buffer)
{
return NULL;
}
raw_io = IoCreate(buffer, Functions);
return StreamIo(raw_io);
}

View file

@ -99,7 +99,7 @@ XMLDecode(Stream *stream, bool autofree)
{ {
continue; continue;
} }
XMLFreeElement(e); //XMLFreeElement(e);
} }
ArrayFree(stack); ArrayFree(stack);
XMLFreeLexer(lexer); XMLFreeLexer(lexer);

View file

@ -138,6 +138,7 @@ XMLCrank(XMLexer *lexer)
return NULL; return NULL;
} }
event = XMLCreateRelax(lexer); event = XMLCreateRelax(lexer);
//Log(LOG_INFO, "A %d", lexer->state);
switch (lexer->state) switch (lexer->state)
{ {
@ -213,13 +214,16 @@ XMLCrank(XMLexer *lexer)
break; break;
case XML_STATE_ATTR: case XML_STATE_ATTR:
attrname = XMLParseName(lexer); attrname = XMLParseName(lexer);
//Log(LOG_INFO, "A %d %s", lexer->state, attrname);
if (!attrname) if (!attrname)
{ {
/* TODO: Throw error */ /* TODO: Throw error */
} }
XMLPushElement(lexer, attrname); XMLPushElement(lexer, attrname);
//Log(LOG_INFO, "Reading props...");
props = XMLReadProps(lexer); props = XMLReadProps(lexer);
//Log(LOG_INFO, "Read props!");
XMLSkipSpace(lexer); XMLSkipSpace(lexer);
if (XMLookahead(lexer, "/>", true)) if (XMLookahead(lexer, "/>", true))
@ -516,6 +520,7 @@ XMLReadProps(XMLexer *lexer)
/* A lack of name is totally excepted */ /* A lack of name is totally excepted */
break; break;
} }
//Log(LOG_INFO, "K=%s...", name);
value = StrDuplicate(""); value = StrDuplicate("");
if (XMLookahead(lexer, "=", true)) if (XMLookahead(lexer, "=", true))
@ -719,18 +724,23 @@ XMLParseAttQuote(XMLexer *lexer)
while ((c = XMLGetc(lexer))) while ((c = XMLGetc(lexer)))
{ {
//Log(LOG_INFO, "E1=%c", c);
if (c == '&') if (c == '&')
{ {
char *code = NULL; char *code = NULL;
int c2; int c2;
int p2 = XMLInitialiseBuffer(lexer); int p2 = XMLInitialiseBuffer(lexer);
while ((c2 = XMLGetc(lexer))) while ((c2 = XMLGetc(lexer)) && c2 != EOF)
{ {
if (c2 == ';') if (c2 == ';')
{ {
break; break;
} }
} }
if (c2 != ';')
{
XMLReset(lexer, p2);
}
} }
else if (!IsNormalQ(c)) else if (!IsNormalQ(c))
{ {
@ -759,18 +769,23 @@ XMLParseAttDouble(XMLexer *lexer)
while ((c = XMLGetc(lexer))) while ((c = XMLGetc(lexer)))
{ {
//Log(LOG_INFO, "E2=%c", c);
if (c == '&') if (c == '&')
{ {
char *code = NULL; char *code = NULL;
int c2; int c2;
int p2 = XMLInitialiseBuffer(lexer); int p2 = XMLInitialiseBuffer(lexer);
while ((c2 = XMLGetc(lexer))) while ((c2 = XMLGetc(lexer)) && c2 != EOF)
{ {
if (c2 == ';') if (c2 == ';')
{ {
break; break;
} }
} }
if (c2 != ';')
{
XMLReset(lexer, p2);
}
continue; continue;
} }
else if (!IsNormalD(c)) else if (!IsNormalD(c))

View file

@ -124,6 +124,83 @@ MessageStanza(ParseeData *args, XMLElement *stanza)
return true; return true;
} }
#define DISCO "http://jabber.org/protocol/disco#info"
static 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 = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO);
{
XMLElement *feature;
#define AdvertiseSimple(f) do \
{ \
feature = XMLCreateTag("feature"); \
XMLAddAttr(feature, "var", f); \
XMLAddChild(query, feature); \
} \
while (0)
AdvertiseSimple("urn:xmpp:reply:0");
AdvertiseSimple("urn:xmpp:styling:0");
AdvertiseSimple("urn:parsee:x-parsee:0");
AdvertiseSimple("urn:parsee:jealousy:0");
/* TODO: Advertise more things */
#undef AdvertiseSimple
}
XMLAddChild(iq_reply, query);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq_reply);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply);
}
static void
IQGet(ParseeData *args, XMLElement *stanza)
{
XMPPComponent *jabber = args->jabber;
if (XMLookForTKV(stanza, "query", "xmlns", DISCO))
{
IQDiscoGet(args, jabber, stanza);
}
}
#undef DISCO
static void
IQStanza(ParseeData *args, XMLElement *stanza)
{
char *type;
type = HashMapGet(stanza->attrs, "type");
#define OnType(ctyp, callback) do \
{ \
if (StrEquals(type, #ctyp)) \
{ \
callback(args, stanza); \
return; \
} \
} \
while (0)
OnType(get, IQGet);
#undef OnType
}
void * void *
ParseeXMPPThread(void *argp) ParseeXMPPThread(void *argp)
{ {
@ -177,10 +254,7 @@ ParseeXMPPThread(void *argp)
} }
else if (StrEquals(stanza->name, "iq")) else if (StrEquals(stanza->name, "iq"))
{ {
/* TODO: Ought to manage info/query requests */ IQStanza(args, stanza);
Log(LOG_WARNING, "Unimplemented 'iq' stanza.");
Log(LOG_WARNING, "Odds are, this is the programmer's fault");
Log(LOG_WARNING, "and this needs to be implemented later on.");
} }
else else
{ {

View file

@ -163,4 +163,8 @@ extern void ParseeCleanup(void *data);
/* Finds the offset of the first line without a '>' at its start. */ /* Finds the offset of the first line without a '>' at its start. */
extern int ParseeFindDatastart(char *data); extern int ParseeFindDatastart(char *data);
/* XMPP-ifies a message event to XEP-0393 if possible. */
extern char * ParseeXMPPify(HashMap *event);
#endif #endif

View file

@ -0,0 +1,13 @@
#ifndef PARSEE_STRSTREAM_H
#define PARSEE_STRSTREAM_H
#include <Cytoplasm/Stream.h>
/* Creates a string stream writer. The referenced buffer must be in the heap,
* or NULL. */
extern Stream * StrStreamWriter(char **buffer);
/* Creates a string stream reader. The referenced buffer may be everywhere. */
extern Stream * StrStreamReader(char *buffer);
#endif