From 771c3271adc949b85e40cf1319c8cea424ed47dc Mon Sep 17 00:00:00 2001 From: LDA Date: Mon, 24 Jun 2024 18:26:08 +0200 Subject: [PATCH] [ADD/WIP] Basic XEP-0393 support. Still needs lots of work. Did I fail to mention I _hate_ HTML? --- src/Main.c | 3 +- src/MatrixEventHandler.c | 11 ++- src/Parsee/Data.c | 153 +++++++++++++++++++++++++++++++++++++ src/Streams/Reader.c | 131 +++++++++++++++++++++++++++++++ src/Streams/Writer.c | 64 ++++++++++++++++ src/XML/Parser.c | 2 +- src/XML/SAX.c | 19 ++++- src/XMPPThread.c | 82 +++++++++++++++++++- src/include/Parsee.h | 4 + src/include/StringStream.h | 13 ++++ 10 files changed, 473 insertions(+), 9 deletions(-) create mode 100644 src/Streams/Reader.c create mode 100644 src/Streams/Writer.c create mode 100644 src/include/StringStream.h diff --git a/src/Main.c b/src/Main.c index 6be7a24..7f1d50d 100644 --- a/src/Main.c +++ b/src/Main.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ Main(void) Stream *yaml; Cron *cron = NULL; + memset(&conf, 0, sizeof(conf)); Log(LOG_INFO, "%s - v%s (Cytoplasm %s)", NAME, VERSION, CytoplasmGetVersionStr() @@ -66,7 +68,6 @@ Main(void) ASRegisterUser(parsee_conf, parsee_conf->sender_localpart); - memset(&conf, 0, sizeof(conf)); conf.port = parsee_conf->port; conf.threads = 4; conf.maxConnections = 32; diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 2913082..7cc0e8e 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -73,12 +73,14 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *sender = GrabString(event, 1, "sender"); char *chat_id, *muc_id, *jid; char *reply_id = MatrixGetReply(event); + char *xepd = ParseeXMPPify(event); bool direct = false; if (ParseeIsPuppet(data->config, sender)) { Free(reply_id); + Free(xepd); return; } @@ -101,6 +103,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Free(local); Free(reply_id); + Free(xepd); return; } @@ -110,6 +113,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) if (!chat_id) { Free(reply_id); + Free(xepd); return; } jid = ParseeEncodeMXID(sender); @@ -125,7 +129,11 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Log(LOG_INFO, "Replying to %s by %s", stanza, sender); } 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(name); Free(stanza); @@ -135,6 +143,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Free(muc_id); Free(jid); Free(reply_id); + Free(xepd); } void diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 08de33e..d66545d 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -132,3 +132,156 @@ ParseeFindDatastart(char *data) return (int) (startline - data); } + +#include + +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, ""); + elem = XMLDecode(StrStreamReader(html), true); + + xepd = XMPPifyElement(elem); + + XMLFreeElement(elem); + Free(html); + + return xepd; +} diff --git a/src/Streams/Reader.c b/src/Streams/Reader.c new file mode 100644 index 0000000..27d92d1 --- /dev/null +++ b/src/Streams/Reader.c @@ -0,0 +1,131 @@ +#include + +#include +#include +#include +#include + +#include + +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); +} diff --git a/src/Streams/Writer.c b/src/Streams/Writer.c new file mode 100644 index 0000000..88bc7ee --- /dev/null +++ b/src/Streams/Writer.c @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include + +#include + +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); +} diff --git a/src/XML/Parser.c b/src/XML/Parser.c index d248f06..bf638ff 100644 --- a/src/XML/Parser.c +++ b/src/XML/Parser.c @@ -99,7 +99,7 @@ XMLDecode(Stream *stream, bool autofree) { continue; } - XMLFreeElement(e); + //XMLFreeElement(e); } ArrayFree(stack); XMLFreeLexer(lexer); diff --git a/src/XML/SAX.c b/src/XML/SAX.c index 6730e61..f60c22a 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -138,6 +138,7 @@ XMLCrank(XMLexer *lexer) return NULL; } event = XMLCreateRelax(lexer); + //Log(LOG_INFO, "A %d", lexer->state); switch (lexer->state) { @@ -213,13 +214,16 @@ XMLCrank(XMLexer *lexer) break; case XML_STATE_ATTR: attrname = XMLParseName(lexer); + //Log(LOG_INFO, "A %d %s", lexer->state, attrname); if (!attrname) { /* TODO: Throw error */ } XMLPushElement(lexer, attrname); + //Log(LOG_INFO, "Reading props..."); props = XMLReadProps(lexer); + //Log(LOG_INFO, "Read props!"); XMLSkipSpace(lexer); if (XMLookahead(lexer, "/>", true)) @@ -516,6 +520,7 @@ XMLReadProps(XMLexer *lexer) /* A lack of name is totally excepted */ break; } + //Log(LOG_INFO, "K=%s...", name); value = StrDuplicate(""); if (XMLookahead(lexer, "=", true)) @@ -719,18 +724,23 @@ XMLParseAttQuote(XMLexer *lexer) while ((c = XMLGetc(lexer))) { + //Log(LOG_INFO, "E1=%c", c); if (c == '&') { char *code = NULL; int c2; int p2 = XMLInitialiseBuffer(lexer); - while ((c2 = XMLGetc(lexer))) + while ((c2 = XMLGetc(lexer)) && c2 != EOF) { if (c2 == ';') { break; } } + if (c2 != ';') + { + XMLReset(lexer, p2); + } } else if (!IsNormalQ(c)) { @@ -759,18 +769,23 @@ XMLParseAttDouble(XMLexer *lexer) while ((c = XMLGetc(lexer))) { + //Log(LOG_INFO, "E2=%c", c); if (c == '&') { char *code = NULL; int c2; int p2 = XMLInitialiseBuffer(lexer); - while ((c2 = XMLGetc(lexer))) + while ((c2 = XMLGetc(lexer)) && c2 != EOF) { if (c2 == ';') { break; } } + if (c2 != ';') + { + XMLReset(lexer, p2); + } continue; } else if (!IsNormalD(c)) diff --git a/src/XMPPThread.c b/src/XMPPThread.c index ab20d0c..a00541b 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -124,6 +124,83 @@ MessageStanza(ParseeData *args, XMLElement *stanza) 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 * ParseeXMPPThread(void *argp) { @@ -177,10 +254,7 @@ ParseeXMPPThread(void *argp) } else if (StrEquals(stanza->name, "iq")) { - /* TODO: Ought to manage info/query requests */ - 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."); + IQStanza(args, stanza); } else { diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 14e832d..38b8fd5 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -163,4 +163,8 @@ extern void ParseeCleanup(void *data); /* Finds the offset of the first line without a '>' at its start. */ extern int ParseeFindDatastart(char *data); + + +/* XMPP-ifies a message event to XEP-0393 if possible. */ +extern char * ParseeXMPPify(HashMap *event); #endif diff --git a/src/include/StringStream.h b/src/include/StringStream.h new file mode 100644 index 0000000..7f43313 --- /dev/null +++ b/src/include/StringStream.h @@ -0,0 +1,13 @@ +#ifndef PARSEE_STRSTREAM_H +#define PARSEE_STRSTREAM_H + +#include + +/* 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