diff --git a/XEPS-TBD.TXT b/XEPS-TBD.TXT index 712c756..441326a 100644 --- a/XEPS-TBD.TXT +++ b/XEPS-TBD.TXT @@ -23,14 +23,11 @@ For future XEPs: federation. As such, if _any_ client devs hear this, please consider adding these, (especially if you're a smElement employee!) - - https://xmpp.org/extensions/xep-0080.html Doxxing people over two protocols is great! - - https://xmpp.org/extensions/xep-0449.html Stickers are great. Matrix and XMPP somewhat has support for them, so might be a nice-to-have, and also to push over XMPP support. - - https://xmpp.org/extensions/xep-0050.html Ad-hoc commands that bridge maintainers can deal with XMPP-style are also a nice to have. @@ -39,10 +36,9 @@ For future XEPs: ON STANDBY BECAUSE THESE HAVE BEEN TERRIBLE TO DEAL WITH AND WHO KEEPS WRITING THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT: - (x) https://xmpp.org/extensions/xep-0084.html + x https://xmpp.org/extensions/xep-0084.html Avatar support would be extremely useful, if just a QoL improvment. Matrix and XMPP both have support for these. - XEP-0084 is a pain in the ass to implement and seems generally just unreliable, however. diff --git a/src/Events.c b/src/Events.c index 48ad604..2a3f5b3 100644 --- a/src/Events.c +++ b/src/Events.c @@ -3,6 +3,8 @@ #include #include +#include + #include HashMap * @@ -25,6 +27,7 @@ HashMap * MatrixCreateMessage(char *body) { HashMap *map; + char *text = NULL; if (!body) { return NULL; @@ -33,6 +36,17 @@ MatrixCreateMessage(char *body) map = HashMapCreate(); HashMapSet(map, "msgtype", JsonValueString("m.text")); HashMapSet(map, "body", JsonValueString(body)); + { + /* TODO */ + XEP393Element *e = XEP393(body); + text = XEP393ToXMLString(e); + XEP393FreeElement(e); + + + HashMapSet(map, "formatted_body", JsonValueString(text)); + HashMapSet(map, "format", JsonValueString("org.matrix.custom.html")); + Free(text); + } return map; } diff --git a/src/Main.c b/src/Main.c index f4dd4d7..e7a56c9 100644 --- a/src/Main.c +++ b/src/Main.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include diff --git a/src/XEP-0393.c b/src/XEP-0393.c new file mode 100644 index 0000000..9954580 --- /dev/null +++ b/src/XEP-0393.c @@ -0,0 +1,291 @@ +#include + +#include +#include +#include + +#include +#include + +static XEP393Element * +CreateElementVessel(XEP393Element *parent, XEP393Type type) +{ + XEP393Element *ret = Malloc(sizeof(*ret)); + ret->parent = parent; + ret->type = type; + ret->children = ArrayCreate(); + ret->text_data = NULL; + + if (parent) + { + ArrayAdd(parent->children, ret); + } + return ret; +} +static void +XEP393FreeElementBase(XEP393Element *element, bool unlink) +{ + size_t i, len; + if (!element) + { + return; + } + + len = ArraySize(element->children); + for (i = 0; i < len; i++) + { + XEP393FreeElement(ArrayGet(element->children, i)); + } + + if (element->parent && unlink) + { + XEP393Element *parent = element->parent; + len = ArraySize(parent->children); + for (i = 0; i < len; i++) + { + XEP393Element *c = ArrayGet(parent->children, i); + if (c == element) + { + ArrayDelete(parent->children, i); + break; + } + } + } + + ArrayFree(element->children); + Free(element->text_data); + Free(element); +} +void +XEP393FreeElement(XEP393Element *element) +{ + XEP393FreeElementBase(element, false); +} + +typedef struct StrView { + char *start; + char *end; + + bool heap_free; +} StrView; +#define ViewLength(v) ((size_t) ((v.end) - (v.start))) +static char * +StringifyView(StrView v) +{ + char *r; + size_t len; + if (!v.start || v.start > v.end) + { + return NULL; + } + + len = ViewLength(v); + r = Malloc(len + 1); + memcpy(r, v.start, len); + r[len] = '\0'; + + return r; +} +static StrView +CreateStaticView(char *str) +{ + StrView view = { + .start = str, + .end = str + strlen(str), + .heap_free = false + }; + + return view; +} +static bool +IdentifySpan(char span_tag, StrView in, StrView *view) +{ + size_t length; + bool found = false; + char prev = '\0'; + if (in.start >= in.end) + { + return false; + } + if (ViewLength(in) < 2) + { + return false; + } + + if (*in.start != span_tag || isspace(*(in.start + 1))) + { + /* The opening styling directive MUST NOT be followed + * by a whitespace character */ + return false; + } + view->start = in.start + 1; + in.start += 1; + + for (length = 0; ViewLength(in) > 0; length++, in.start++) + { + if (*in.start == span_tag) + { + found = true; + break; + } + + prev = *in.start; + } + if (!found || !length || (prev && isspace(prev))) + { + /* the closing styling directive MUST NOT be preceeded + * by a whitespace character. */ + return false; + } + + view->end = in.start; + return true; +} +static void +XEP393Decode(StrView view, XEP393Element *root) +{ + StrView subview = view; + StrView textview = view; + XEP393Element *text, *span; + size_t i; + bool managed = false; + + textview.end = subview.start; + for (i = 0; subview.start < subview.end; subview.start++) + { + StrView span_view; + managed = false; +#define Spanify(xep_symbol) \ + managed = true; \ + textview.end = subview.start; \ + text = CreateElementVessel( \ + root, XEP393_TEXT \ + ); \ + text->text_data = StringifyView(textview); \ + \ + /* Found a span. */ \ + span = CreateElementVessel( \ + root, xep_symbol \ + ); \ + \ + XEP393Decode(span_view, span); \ + \ + /* Update subview */ \ + subview.start = span_view.end + 1; \ + \ + /* Update textview */ \ + textview.start = subview.start; \ + textview.end = subview.start + if (IdentifySpan('_', subview, &span_view)) + { + Spanify(XEP393_ITALIC); + } + else if (IdentifySpan('*', subview, &span_view)) + { + Spanify(XEP393_EMPH); + } + else if (IdentifySpan('`', subview, &span_view)) + { + Spanify(XEP393_MONO); + } + else + { + /* Text character: update end */ + textview.end = subview.start; + } + (void) i; + } + + if (!managed) + { + textview.end = subview.start; + text = CreateElementVessel( + root, XEP393_TEXT + ); + text->text_data = StringifyView(textview); + } +} + +XEP393Element * +XEP393(char *message) +{ + StrView view = CreateStaticView(message); + XEP393Element *root = CreateElementVessel(NULL, XEP393_ROOT); + + /* TODO: Parse blocks first, *then* spans. Considering the + * current architecture, this shouldn't be too hard to integrate, + * given how string views already manage boundaries, and elements + * can already be used to contain blocks I think. */ + XEP393Decode(view, root); + return root; +} + +#include + +static void +ShoveXML(XEP393Element *element, XMLElement *xmlparent) +{ + XMLElement *head = xmlparent; + size_t i; + if (!element || !xmlparent) + { + return; + } + + switch (element->type) + { + case XEP393_ITALIC: + head = XMLCreateTag("i"); + XMLAddChild(xmlparent, head); + break; + case XEP393_EMPH: + head = XMLCreateTag("strong"); + XMLAddChild(xmlparent, head); + break; + case XEP393_MONO: + head = XMLCreateTag("code"); + XMLAddChild(xmlparent, head); + break; + default: break; + } + if (ArraySize(element->children) == 0 && element->text_data) + { + XMLElement *text = XMLCreateText(element->text_data); + XMLAddChild(head, text); + } + + for (i = 0; i < ArraySize(element->children); i++) + { + XEP393Element *sub = ArrayGet(element->children, i); + ShoveXML(sub, head); + } +} + +#include +char * +XEP393ToXMLString(XEP393Element *xepd) +{ + XMLElement *root; + + Stream *writer; + char *ret = NULL; + size_t i; + if (!xepd) + { + return NULL; + } + + root = XMLCreateTag("ROOT"); + ShoveXML(xepd, root); + + writer = StrStreamWriter(&ret); + for (i = 0; i < ArraySize(root->children); i++) + { + XMLEncode(writer, ArrayGet(root->children, i)); + } + XMLFreeElement(root); + StreamFlush(writer); + StreamClose(writer); + + return ret; +} diff --git a/src/XMPPThread.c b/src/XMPPThread.c index a167759..5f62aa0 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -17,8 +17,6 @@ #include #include -/* TODO: Rewrite this avatar code. - * XEP-0084 sucks. */ #define IQ_ADVERT \ AdvertiseSimple("http://jabber.org/protocol/chatstates") \ AdvertiseSimple("http://jabber.org/protocol/caps") \ @@ -42,7 +40,6 @@ AdvertiseSimple("urn:parsee:x-parsee:0") \ AdvertiseSimple("urn:parsee:jealousy:0") -/* TODO: More identities */ #define IQ_IDENTITY \ IdentitySimple("gateway", "matrix", "Parsee Matrix Gateway") \ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ @@ -286,7 +283,11 @@ ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza) } struct XMPPThread; typedef struct XMPPThreadInfo { - /* A FIFO of stanzas */ + /* A FIFO of stanzas inbound, to be read by dispatcher + * threads. + * + * TODO: Using it's length in !stats can be useful + * for having a "congestion" metric for Parsee admins... */ Array *stanzas; pthread_mutex_t lock; @@ -1022,6 +1023,16 @@ IQResult(ParseeData *args, XMLElement *stanza) } } } +static XMLElement * +CreateTagWithText(const char *tn, char *text) +{ + XMLElement *tag = XMLCreateTag((char *) tn); + XMLElement *tex = XMLCreateText(text); + + XMLAddChild(tag, tex); + + return tag; +} static void IQGet(ParseeData *args, XMLElement *stanza) { @@ -1033,6 +1044,44 @@ IQGet(ParseeData *args, XMLElement *stanza) 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)) { @@ -1228,7 +1277,6 @@ PresenceStanza(ParseeData *args, XMLElement *stanza) JsonValue *val = JsonValueInteger(power_level); JsonValueFree(JsonSet(users, val, 1, matrix_user_pl)); ASSetPL(args->config, room, powers); - JsonValueFree(val); } else { @@ -1283,8 +1331,11 @@ PresenceStanza(ParseeData *args, XMLElement *stanza) status_str = status_data->data; } - /* TODO: Verify whenever this code works as soon as I can get - * my own instance (kappach.at) with presence enabled. */ + /* TODO: "The server will automatically set a user's presence to + * unavailable if their last active time was over a threshold value + * (e.g. 5 minutes)." + * We _will_ need to manage those cases properly(cronjob?) if we want + * XMPP presences to sync properly */ ASSetStatus( args->config, from_matrix, GuessStatus(stanza), status_str @@ -1547,6 +1598,7 @@ ParseeAwaitStanza(char *identifier, int64_t timeout) return NULL; } + /* Convert into an absolute timeout */ if (timeout > 0) { int64_t seconds = timeout / (1 SECONDS); diff --git a/src/include/XEP393.h b/src/include/XEP393.h new file mode 100644 index 0000000..72a6a04 --- /dev/null +++ b/src/include/XEP393.h @@ -0,0 +1,43 @@ +#ifndef PARSEE_XEP393_H +#define PARSEE_XEP393_H + +#include + +typedef enum XEP393Type { + XEP393_ROOT, + XEP393_ITALIC, + XEP393_EMPH, + XEP393_MONO, + XEP393_TEXT +} XEP393Type; +typedef struct XEP393Element { + struct XEP393Element *parent; + + XEP393Type type; + + char *text_data; + Array *children; +} XEP393Element; + +/** Tries to decode a XEP-393 message into a formatted structure. + * -------- + * Returns: A root XEP393Element[LA:HEAP] | NULL + * Modifies: NOTHING + * See-Also: https://xmpp.org/extensions/xep-0393.html, XEP393FreeElement */ +extern XEP393Element * XEP393(char *message); + +/** Frees an XEP-0393 {element}, alongside all it's children + * -------- + * Returns: NOTHING + * Modifies: {element} + * See-Also: https://xmpp.org/extensions/xep-0393.html, XEP393 */ +extern void XEP393FreeElement(XEP393Element *element); + +/** Converts a XEP-0393 element to a XMLd string + * -------- + * Returns: A string representing a XML'ified version of {xepd}[LA:HEAP] + * Modifies: NOTHING + * See-Also: https://xmpp.org/extensions/xep-0393.html, XEP393FreeElement, XEP393 */ +extern char * XEP393ToXMLString(XEP393Element *xepd); + +#endif