From ca87972b3a65b28f117fa4f6e1c31104daae20cd Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 16 Nov 2024 14:11:32 +0100 Subject: [PATCH 01/45] [ADD/WIP] Push all the Janet changes This is still unstable(and I still need to design/document the exposed API)! Do(n't) go and use it! --- .gitignore | 8 + configure.c | 32 +- src/Commands/SetPL.c | 2 +- src/Extensions.c | 74 ++++ src/Extensions/DB.c | 366 +++++++++++++++++++ src/Extensions/Data.c | 222 ++++++++++++ src/Extensions/Functions.c | 207 +++++++++++ src/Extensions/Functions.h | 34 ++ src/Extensions/Http.c | 258 +++++++++++++ src/Extensions/Internal.h | 98 +++++ src/Extensions/JSON.c | 170 +++++++++ src/Extensions/Jabber.c | 30 ++ src/Extensions/Janet.c | 304 ++++++++++++++++ src/Extensions/KV.c | 86 +++++ src/Extensions/Matrix.c | 149 ++++++++ src/Extensions/PushEvents.c | 89 +++++ src/Extensions/PushStanza.c | 98 +++++ src/Extensions/RequestCmds.c | 580 ++++++++++++++++++++++++++++++ src/Extensions/StanzaInfo.c | 98 +++++ src/Extensions/XML.c | 357 ++++++++++++++++++ src/HttParsee.c | 3 + src/Main.c | 16 + src/Parsee/Config.c | 3 + src/Parsee/Data.c | 10 + src/Parsee/Utils/Formatting.c | 5 +- src/Routes/Transactions.c | 4 + src/XMPPCommand/Commands.c | 23 +- src/XMPPCommand/Internal.h | 17 + src/XMPPCommand/Manager.c | 18 +- src/XMPPCommands/AddAdmin.c | 4 +- src/XMPPCommands/AddNofly.c | 4 +- src/XMPPCommands/Admins.c | 4 +- src/XMPPCommands/Cleanup.c | 3 +- src/XMPPCommands/DelAdmin.c | 8 +- src/XMPPCommands/MUCInformation.c | 6 +- src/XMPPCommands/MUCKV.c | 6 +- src/XMPPCommands/MUCUnlink.c | 3 +- src/XMPPCommands/Nofly.c | 3 +- src/XMPPCommands/Status.c | 3 +- src/XMPPCommands/Whitelist.c | 9 +- src/XMPPThread/PEPs/VCard.c | 4 +- src/XMPPThread/ReListener.c | 52 +-- src/XMPPThread/Stanzas/IQ.c | 7 + src/XMPPThread/Stanzas/Message.c | 12 +- src/XMPPThread/Stanzas/Presence.c | 10 +- src/XMPPThread/internal.h | 4 - src/include/Extension.h | 81 +++++ src/include/Parsee.h | 27 +- src/include/XMPPCommand.h | 26 +- src/include/XMPPCommands.x.h | 5 - 50 files changed, 3550 insertions(+), 92 deletions(-) create mode 100644 src/Extensions.c create mode 100644 src/Extensions/DB.c create mode 100644 src/Extensions/Data.c create mode 100644 src/Extensions/Functions.c create mode 100644 src/Extensions/Functions.h create mode 100644 src/Extensions/Http.c create mode 100644 src/Extensions/Internal.h create mode 100644 src/Extensions/JSON.c create mode 100644 src/Extensions/Jabber.c create mode 100644 src/Extensions/Janet.c create mode 100644 src/Extensions/KV.c create mode 100644 src/Extensions/Matrix.c create mode 100644 src/Extensions/PushEvents.c create mode 100644 src/Extensions/PushStanza.c create mode 100644 src/Extensions/RequestCmds.c create mode 100644 src/Extensions/StanzaInfo.c create mode 100644 src/Extensions/XML.c create mode 100644 src/XMPPCommand/Internal.h create mode 100644 src/include/Extension.h diff --git a/.gitignore b/.gitignore index 05919ab..01d033d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,11 @@ tags !.forgejo !.forgejo/* !.forgejo/** + +# Janet +janet/* +janet/** +janet +extensions +extensions/* +extensions/** diff --git a/configure.c b/configure.c index 48b541b..2d4fa3e 100644 --- a/configure.c +++ b/configure.c @@ -367,7 +367,7 @@ write_objects(FILE *makefile, str_array_t *sources) { char *src = str_array_get(sources, i); char *ofl = string_rep_ext(src, ".c", ".o"); - char *obj = string_cat("build", ofl + 3); + char *obj = string_cat("build", strchr(ofl, '/')); fprintf(makefile, " %s", obj); free(ofl); @@ -440,7 +440,8 @@ main_build(int argc, char *argv[]) ssize_t nread; bool with_static = false, with_lmdb = false; int opt; - str_array_t *sources, *images, *utils, *aya; + str_array_t *sources, *janet = NULL, *images, *utils, *aya; + char *janet_dir = NULL; if (repo) { @@ -455,7 +456,7 @@ main_build(int argc, char *argv[]) repo = strdup("N/A"); } - while ((opt = getopt(argc, argv, "sl")) != -1) + while ((opt = getopt(argc, argv, "slJ:")) != -1) { switch (opt) { @@ -466,6 +467,9 @@ main_build(int argc, char *argv[]) with_lmdb = with_static; with_static = true; break; + case 'J': + janet_dir = strdup(optarg); + break; } } @@ -484,13 +488,22 @@ main_build(int argc, char *argv[]) /* Create all objects */ sources = collect_sources("src", true, ".c"); + if (janet_dir) + { + janet = collect_sources(janet_dir, true, ".c"); + for (i = 0; i < str_array_len(janet); i++) + { + str_array_add(sources, str_array_get(janet, i)); + } + str_array_free(janet); + } images = collect_sources("etc/media", true, ".png"); fprintf(makefile, "binary:"); write_objects(makefile, sources); write_images(makefile, images); fprintf(makefile, "\n\t"); { - fprintf(makefile, "$(CC) -o $(BINARY)"); + fprintf(makefile, "$(CC) -lm -o $(BINARY)"); if (with_static) { fprintf(makefile, " -static"); @@ -503,7 +516,7 @@ main_build(int argc, char *argv[]) fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); if (with_lmdb) fprintf(makefile, " -llmdb"); } - fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n"); + fprintf(makefile, " -lCytoplasm -lm $(LDFLAGS)\n"); } /* Write rules for every source */ @@ -511,7 +524,7 @@ main_build(int argc, char *argv[]) { char *src = str_array_get(sources, i); char *ofl = string_rep_ext(src, ".c", ".o"); - char *obj = string_cat("build", ofl + 3); + char *obj = string_cat("build", strchr(ofl, '/')); fprintf(makefile, "%s: %s\n", obj, src); { @@ -527,6 +540,10 @@ main_build(int argc, char *argv[]) str_array_free(s); fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) "); + if (janet_dir) + { + fprintf(makefile, "-DJANET -I %s ", janet_dir); + } fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" "); fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" "); fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" "); @@ -597,7 +614,7 @@ main_build(int argc, char *argv[]) fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); if (with_lmdb) fprintf(makefile, " -llmdb"); } - fprintf(makefile, " -lCytoplasm $(CFLAGS)\n"); + fprintf(makefile, " -lCytoplasm -lm $(CFLAGS)\n"); } free(ofl); @@ -685,6 +702,7 @@ main_build(int argc, char *argv[]) str_array_free(aya); fflush(makefile); fclose(makefile); + free(janet_dir); free(repo); return EXIT_SUCCESS; } diff --git a/src/Commands/SetPL.c b/src/Commands/SetPL.c index ef2fdb6..95a2f04 100644 --- a/src/Commands/SetPL.c +++ b/src/Commands/SetPL.c @@ -12,7 +12,7 @@ CommandHead(CmdSetPL, cmd, argp) char *user = HashMapGet(cmd->arguments, "user"); char *room = HashMapGet(cmd->arguments, "room"); char *pl_str = HashMapGet(cmd->arguments, "pl"); - long pl = strtol(pl_str, NULL, 10); + long pl = pl_str ? strtol(pl_str, NULL, 10) : 0; HashMap *map; diff --git a/src/Extensions.c b/src/Extensions.c new file mode 100644 index 0000000..987ec91 --- /dev/null +++ b/src/Extensions.c @@ -0,0 +1,74 @@ +#ifndef JANET +#include +#include + +bool +ExtensionEnabled(void) +{ + return false; +} +Extensions * +ExtensionCreateContext(ParseeData *data) +{ + (void) data; + return NULL; +} +void +ExtensionDestroyContext(Extensions *ctx) +{ + (void) ctx; +} +Extension * +ExtensionLoadBasic(Extensions *ctx, char *id, char *file) +{ + (void) file; + (void) ctx; + (void) id; + return NULL; +} +void +ExtensionLoadDir(Extensions *ctx, char *dir) +{ + (void) ctx; + (void) dir; +} +bool +ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType type) +{ + (void) ctx; + (void) stanza; + + return false; +} +bool +ExtensionPushEvent(Extensions *ctx, HashMap *obj) +{ + (void) ctx; + (void) obj; +} +void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza) +{ + (void) ctx; + (void) stanza; +} +void +ExtensionRequestCommands(Extensions *ctx) +{ + (void) ctx; +} +void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza) +{ + (void) ctx; + (void) jid; + (void) query; + (void) stanza; +} +void +ExtensionReload(Extensions *ctx, char *id) +{ + (void) ctx; + (void) id; +} +#endif diff --git a/src/Extensions/DB.c b/src/Extensions/DB.c new file mode 100644 index 0000000..dd15f0c --- /dev/null +++ b/src/Extensions/DB.c @@ -0,0 +1,366 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +static int JanetDbGet(void *data0, Janet key, Janet *out); +static void JanetMarshalDb(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalDb(JanetMarshalContext *ctx); +static void JanetDbToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashDb(void *data0, size_t size); +static int JanetCloseDb(void *data0, size_t size); +static Janet JanetDbNext(void *data0, Janet next); + +static Janet JanetRefClose(int32_t argc, Janet *argv); +static Janet JanetRefGet(int32_t argc, Janet *argv); +static Janet JanetRefSet(int32_t argc, Janet *argv); +static const JanetMethod db_methods[] = { + { "get", JanetRefGet }, + { "set", JanetRefSet }, + { "close", JanetRefClose }, + + { NULL, NULL } +}; +const JanetAbstractType janet_db_type = { + .name = "db-ref", + .gc = JanetCloseDb, .gcmark = NULL, + + .get = JanetDbGet, .put = NULL, + .marshal = JanetMarshalDb, .unmarshal = JanetUnmarshalDb, + .tostring = JanetDbToString, + + .compare = NULL, + .hash = JanetHashDb, + + .next = JanetDbNext, + JANET_ATEND_NEXT +}; + +typedef struct DbAndRef { + Db *database; + DbRef *ref; + + bool valid; +} DbAndRef; + +static Janet +CreateFromDb(Db *db, DbRef *ref) +{ + JanetAbstract *abs; + + abs = janet_abstract(&janet_db_type, sizeof(DbAndRef)); + ((DbAndRef *)(abs))->database = db; + ((DbAndRef *)(abs))->ref = ref; + ((DbAndRef *)(abs))->valid = true; + + return janet_wrap_abstract(abs); +} + +static void +CloseFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + if (indirect) + { + indirect->valid = false; + indirect->database = NULL; + indirect->ref = NULL; + } +} +static DbRef * +RefFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + return (indirect && indirect->valid) ? indirect->ref : NULL; +} +static Db * +DbFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + return (indirect && indirect->valid) ? indirect->database : NULL; +} + +static int +JanetDbGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, db_methods, out); +} +static void +JanetMarshalDb(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalDb(JanetMarshalContext *ctx) +{ + DbAndRef *data = janet_unmarshal_abstract(ctx, sizeof(DbAndRef)); + return data; +} +static int +JanetCloseDb(void *data0, size_t size) +{ + DbAndRef *data = data0; + if (data && data->valid) + { + data->valid = false; + DbUnlock(data->database, data->ref); + data->database = NULL; + data->ref = NULL; + } + return 0; +} +static void +JanetDbToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " database reference]"); +} +static int32_t +JanetHashDb(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* Hm. */ + return (int32_t) datap; +} +static Janet +JanetDbNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(db_methods, next); +} + +static Janet +JanetRefClose(int32_t argc, Janet *argv) +{ + Db *db; + DbRef *ref; + janet_fixarity(argc, 1); + + if (!(ref = RefFromJanet(argv[0])) || + !(db = DbFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + + DbUnlock(db, ref); + CloseFromJanet(argv[0]); + return janet_wrap_true(); + +} +static Janet +JanetRefGet(int32_t argc, Janet *argv) +{ + DbRef *ref; + HashMap *json; + janet_fixarity(argc, 1); + + if (!(ref = RefFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + + json = DbJson(ref); + return JsonToJanet(json); +} +static Janet +JanetRefSet(int32_t argc, Janet *argv) +{ + DbRef *ref; + JanetTable *table; + HashMap *json; + bool ret; + + janet_fixarity(argc, 2); + + if (!(ref = RefFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_TABLE)) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("object to replace isnt JSON") + ); + return janet_wrap_nil(); + } + + table = janet_unwrap_table(argv[1]); + if (!(json = JanetToJson(table))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("object to replace isnt JSON") + ); + return janet_wrap_nil(); + } + + ret = DbJsonSet(ref, json); + JsonFree(json); + + return janet_wrap_boolean(ret); +} + + +Janet +JanetDbExists(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + int32_t i; + bool ret; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ret = DbExistsArgs(data->db, args); + ArrayFree(args); + + return janet_wrap_boolean(ret); +} +Janet +JanetDbList(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args, *list; + JanetArray *arr; + int32_t i; + janet_arity(argc, 1, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + list = DbListArgs(data->db, args); + arr = janet_array(0); + for (i = 0; i < (int32_t) ArraySize(list); i++) + { + char *ent = ArrayGet(list, i); + janet_array_push(arr, janet_cstringv(ent)); + } + + ArrayFree(args); + DbListFree(list); + return janet_wrap_array(arr); +} +Janet +JanetDbLock(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + DbRef *ref; + int32_t i; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ref = DbLockArgs(data->db, args); + ArrayFree(args); + + if (!ref) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("couldn't lock database") + ); + return janet_wrap_nil(); + } + + return CreateFromDb(data->db, ref); +} +Janet +JanetDbCreate(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + DbRef *ref; + int32_t i; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ref = DbCreateArgs(data->db, args); + ArrayFree(args); + + if (!ref) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("couldn't create database") + ); + return janet_wrap_nil(); + } + + return CreateFromDb(data->db, ref); +} +#endif diff --git a/src/Extensions/Data.c b/src/Extensions/Data.c new file mode 100644 index 0000000..71b8d0f --- /dev/null +++ b/src/Extensions/Data.c @@ -0,0 +1,222 @@ +#ifdef JANET + +#include "Extensions/Internal.h" +#include "Extensions/Functions.h" + +static int JanetDataGet(void *data0, Janet key, Janet *out); +static void JanetMarshalData(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalData(JanetMarshalContext *ctx); +static void JanetDataToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashData(void *data0, size_t size); +static Janet JanetDataNext(void *data0, Janet next); + +static Janet JanetShimMXC(int32_t argc, Janet *argv); +static Janet JanetMTO(int32_t argc, Janet *argv); +static Janet JanetIsAdmin(int32_t argc, Janet *argv); +static Janet JanetUptime(int32_t argc, Janet *argv); +static Janet JanetGlobalBan(int32_t argc, Janet *argv); +static const JanetMethod data_methods[] = { + /* Matrix */ + { "matrix/invite", JanetMatrixInvite }, + { "matrix/find", JanetMatrixFind }, + { "matrix/send", JanetMatrixSend }, + { "matrix/shim-mxc", JanetShimMXC }, + { "matrix/matrix-to", JanetMTO }, + + /* Bridge */ + { "bridge/get", JanetGetKey }, + { "bridge/set", JanetSetKey }, + { "bridge/stanza-info", JanetStanzaInfo }, + + /* XMPP */ + { "xmpp/write", JanetWriteStanza }, + + /* Database */ + { "db/lock", JanetDbLock }, + { "db/exists?", JanetDbExists }, + { "db/create", JanetDbCreate }, + { "db/list", JanetDbList }, + + /* General */ + { "admin?", JanetIsAdmin }, + { "uptime", JanetUptime }, + { "global-ban", JanetGlobalBan }, + + { NULL, NULL } +}; + +const JanetAbstractType janet_data_type = { + .name = "parsee-ctx", + .gc = NULL, .gcmark = NULL, + + .get = JanetDataGet, .put = NULL, + .marshal = JanetMarshalData, .unmarshal = JanetUnmarshalData, + .tostring = JanetDataToString, + + .compare = NULL, + .hash = JanetHashData, + + .next = JanetDataNext, + JANET_ATEND_NEXT +}; + +ParseeData * +GetParseeData(Janet v) +{ + ParseeData **indirect = janet_checkabstract(v, &janet_data_type); + return indirect ? *indirect : NULL; +} +Janet +FromParseeData(ParseeData *data) +{ + JanetAbstract abs; + if (!data) + { + return janet_wrap_nil(); + } + + abs = janet_abstract(&janet_data_type, sizeof(ParseeData *)); + *((ParseeData **) abs) = data; + return janet_wrap_abstract(abs); +} + +static int +JanetDataGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, data_methods, out); +} +static void +JanetMarshalData(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalData(JanetMarshalContext *ctx) +{ + ParseeData **data = janet_unmarshal_abstract(ctx, sizeof(ParseeData *)); + return data ? *data : NULL; +} +static void +JanetDataToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " context type]"); +} +static int32_t +JanetHashData(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* We're casting down, but in a context where there is really + * just one ParseeData, is that really such a crime? */ + return (int32_t) datap; +} +static Janet +JanetDataNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(data_methods, next); +} +static Janet +JanetIsAdmin(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *str; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + str = (char *) janet_to_string(argv[1]); + + return janet_wrap_boolean(ParseeIsAdmin(data, str)); +} +static Janet +JanetShimMXC(int32_t argc, Janet *argv) +{ + ParseeData *data; + Janet v; + char *mxc, *shimmed; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + mxc = (char *) janet_to_string(argv[1]); + shimmed = ParseeToUnauth(data, mxc); + if (!shimmed) + { + return janet_wrap_nil(); + } + v = janet_cstringv(shimmed); + Free(shimmed); + + return v; +} +static Janet +JanetMTO(int32_t argc, Janet *argv) +{ + ParseeData *data; + Janet v; + char *common, *mto; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + common = (char *) janet_to_string(argv[1]); + mto = ParseeGenerateMTO(common); + if (!mto) + { + return janet_wrap_nil(); + } + v = janet_cstringv(mto); + Free(mto); + + (void) data; + return v; +} +static Janet +JanetUptime(int32_t argc, Janet *argv) +{ + ParseeData *data; + janet_fixarity(argc, 1); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + + (void) data; + return janet_wrap_number(ParseeUptime()/1000.); +} +static Janet +JanetGlobalBan(int32_t argc, Janet *argv) +{ + ParseeData *data; + const char *user, *reason = NULL; + janet_arity(argc, 2, 3); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (!(user = janet_getcstring(argv, 1))) + { + return janet_wrap_nil(); + } + if (argc == 3 && janet_checktype(argv[2], JANET_STRING)) + { + reason = janet_getcstring(argv, 2); + } + + ParseeGlobalBan(data, (char *) user, (char *) reason); + return janet_wrap_true(); +} +#endif diff --git a/src/Extensions/Functions.c b/src/Extensions/Functions.c new file mode 100644 index 0000000..d159f99 --- /dev/null +++ b/src/Extensions/Functions.c @@ -0,0 +1,207 @@ +#ifdef JANET +#include "Extensions/Internal.h" + +#include "Extensions/Functions.h" + +Janet +JanetMilliseconds(int32_t argc, Janet *argv) +{ + janet_fixarity(argc, 0); + return janet_wrap_number(UtilTsMillis()/1000.); +} +Janet +JanetCytoVersion(int32_t argc, Janet *argv) +{ + janet_fixarity(argc, 0); + return janet_cstringv(CytoplasmGetVersionStr()); +} +static const JanetReg functions[] = { + /* Cytoplasm */ + { + "cyto/version", JanetCytoVersion, + "(cyto/version)\n\n" + "Returns the Cytoplasm version as a string." + }, + { + "cyto/ts", JanetMilliseconds, + "(cyto/ts)\n\n" + "Returns the timestamp as seconds since 01-01-1970." + }, + + /* XML */ + { + "xml/data", JanetCreateXMLData, + "(xml/data [PARENT?] [TEXT])\n\n" + "Creates a data XML element with freeform text." + }, + { + "xml/tag", JanetCreateXMLTag, + "(xml/tag [PARENT?] [NAME])\n\n" + "Creates a simple XML tag with a name" + }, + + /* JSON */ + { + "json/parse", JanetParseJSON, + "(json/parse [INPUT:buffer]) -> table|NIL\n\n" + "Parses a JSON buffer into a table." + }, + + /* HTTP */ + { + "http/request", JanetHttpRequest, + "(http/request " + "[METHOD:string] " + "[URL:string] " + "[HEADERS:table?] " + "[DATA:buffer])\n\n" + "Creates and sends an HTTP request, and returns an HTTP response" + }, + + {NULL, NULL, NULL} +}; +void +SetupBasicExtensionContext(Extension *ext, JanetTable *env) +{ + char *parsee; + /* Constants */ + janet_def(ext->env, + "parsee/ctx", FromParseeData(ext->source->data), + "The Parsee context used, which maps directly to a ParseeData " + "structure pointer in C99land. Used in various functions." + ); + + parsee = ParseeJID(ext->source->data); + janet_def(ext->env, + "parsee/jid", janet_cstringv(parsee), + "The Parsee bot's Jabber ID as a string" + ); + Free(parsee); + + parsee = ParseeMXID(ext->source->data); + janet_def(ext->env, + "parsee/mxid", janet_cstringv(parsee), + "The Parsee bot's MXID as a string" + ); + Free(parsee); + + janet_def(ext->env, + "parsee/id", janet_cstringv(ext->id), + "The extension's 'identifier'(reverse DNS) notation." + ); + + /* Functions */ + janet_cfuns(env, "parsee", functions); + + /* "Why bother with C99?" */ +#define CheckType(var, type, str) \ + "(if (not= (type " #var ") :" #type ") (error `" str "`))" + janet_dostring(ext->env, + "(defn xml/get-attr[xml key] " + CheckType(xml, table, "XML is not a table") + CheckType(key, string, "KEY is not a string") + "(get (get xml :xml-attrs) key) " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xml/set-attr[xml key val] " + CheckType(xml, table, "XML is not a table") + CheckType(key, string, "KEY is not a string") + CheckType(val, string, "VAL is not a string") + "(put (get xml :xml-attrs) key val) " + + /* Just to make sure this function returns NIL */ + "nil" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/create-note [type msg] " + "(def note (xml/tag `note`)) " + "(def mesg (xml/data note msg)) " + + "(xml/set-attr note `type` type) " + + "note" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/create-form [] " + "(def x (xml/tag `x`)) " + "(xml/set-attr x `xmlns` `jabber:x:data`) " + + "x" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/set-form-title [x title] " + "(def title-xml (xml/tag x `title`)) " + "(def title-txt (xml/data title-xml title)) " + + "title-xml" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/generate-table [x fields elems] " + CheckType(x, table, "Form is not a table") + CheckType(fields, table, "Fields is not a table") + CheckType(elems, array, "Elems is not an array") + + "(def reported (xml/tag x `reported`)) " + + "(loop [[id name] :pairs fields] " + "(def field (xml/tag reported `field`)) " + "(xml/set-attr field `var` id) " + "(xml/set-attr field `label` name) " + ") " + + "(loop [entry :in elems] " + CheckType(entry, table, "Entry is not a table.") + "(def xml-entry (xml/tag x `item`)) " + + "(loop [[id val] :pairs entry] " + "(def xml-field (xml/tag xml-entry `field`)) " + "(def xml-value (xml/tag xml-field `value`)) " + "(xml/data xml-value val) " + "(xml/set-attr xml-field `var` id) " + ") " + ") " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/find-tag [xml tag] " + "(var ret nil) " + "(loop [xml-field :in (get xml :xml-children)] " + "(if (= (get xml-field :xml-name) tag) (do" + "(set ret xml-field) " + "(break)" + ")) " + ") " + "ret " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/get-field [submit key] " + CheckType(submit, table, "Input is not a table") + "(var value nil) " + "(loop [xml-field :in (get submit :xml-children)] " + "(if (and (= (get xml-field :xml-name) `field`) (= (get (get xml-field :xml-attrs) `var`) key)) (do" + "(set value (xmppcmd/find-tag xml-field `value`)) " + "(set value (get (get (get value :xml-children) 0) :xml-data)) " + "(break)" + ")) " + ") " + "value " + ") " + , "embed:" __FILE__, NULL + ); + +#undef CheckType +} +#endif diff --git a/src/Extensions/Functions.h b/src/Extensions/Functions.h new file mode 100644 index 0000000..0d0f4a6 --- /dev/null +++ b/src/Extensions/Functions.h @@ -0,0 +1,34 @@ +#ifdef JANET + +#include + +#define Bind(name) Janet Janet##name(int32_t argc, Janet *argv) + + +Bind(Milliseconds); +Bind(CytoVersion); + +Bind(WriteStanza); + +Bind(CreateXMLData); +Bind(CreateXMLTag); +Bind(SetXMLAttr); + +Bind(ParseJSON); + +Bind(MatrixInvite); +Bind(MatrixFind); +Bind(MatrixSend); + +Bind(StanzaInfo); +Bind(GetKey); +Bind(SetKey); + +Bind(DbLock); +Bind(DbCreate); +Bind(DbList); +Bind(DbExists); + +Bind(HttpRequest); + +#endif diff --git a/src/Extensions/Http.c b/src/Extensions/Http.c new file mode 100644 index 0000000..ffd95b8 --- /dev/null +++ b/src/Extensions/Http.c @@ -0,0 +1,258 @@ +#ifdef JANET + +#include "Extensions/Internal.h" +#include "Extensions/Functions.h" + +#include +#include + +static int JanetHttpGet(void *http0, Janet key, Janet *out); +static void JanetMarshalHttp(void *http0, JanetMarshalContext *ctx); +static void *JanetUnmarshalHttp(JanetMarshalContext *ctx); +static void JanetHttpToString(void *http0, JanetBuffer *buf); +static int32_t JanetHashHttp(void *http0, size_t size); +static Janet JanetHttpNext(void *http0, Janet next); +static int JanetCloseHttp(void *data0, size_t size); + +static Janet JanetHttpCode(int32_t argc, Janet *argv); +static Janet JanetHttpHeaders(int32_t argc, Janet *argv); +static Janet JanetHttpResponse(int32_t argc, Janet *argv); +static const JanetMethod http_methods[] = { + { "code", JanetHttpCode }, + { "headers", JanetHttpHeaders }, + { "response", JanetHttpResponse }, + + { NULL, NULL } +}; + +typedef struct HttpStruct { + int code; + HashMap *headers; + + uint8_t *data; + int32_t size; +} HttpStruct; + +const JanetAbstractType janet_http_type = { + .name = "http-response", + .gc = JanetCloseHttp, .gcmark = NULL, + + .get = JanetHttpGet, .put = NULL, + .marshal = JanetMarshalHttp, .unmarshal = JanetUnmarshalHttp, + .tostring = JanetHttpToString, + + .compare = NULL, + .hash = JanetHashHttp, + + .next = JanetHttpNext, + JANET_ATEND_NEXT +}; +Janet +JanetHttpRequest(int32_t argc, Janet *argv) +{ + const char *method_str = NULL; + const char *path_str = NULL; + JanetTable *headers = NULL; + JanetAbstract *abs = NULL; + JanetBuffer *data = NULL; + HttpClientContext *ctx; + HashMap *raw_headers; + char *header, *value; + HttpStruct *http; + int code, ch; + int32_t i; + Uri *url; + + janet_arity(argc, 2, 4); + if (!(method_str = janet_getcstring(argv, 0)) || + !(path_str = janet_getcstring(argv, 1))) + { + return janet_wrap_nil(); + } + if (argc >= 3) + { + headers = janet_gettable(argv, 2); + } + if (argc >= 4) + { + data = janet_getbuffer(argv, 3); + } + + url = UriParse(path_str); + if (!url || + (!StrEquals(url->proto, "http") && !StrEquals(url->proto, "https"))) + { + UriFree(url); + return janet_wrap_nil(); + } + if (!url->port || StrEquals(url->proto, "http")) + { + url->port = 80; + } + if (!url->port || StrEquals(url->proto, "https")) + { + url->port = 443; + } + ctx = HttpRequest( + HttpRequestMethodFromString(method_str), + StrEquals(url->proto, "https") ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, + url->port, url->host, url->path + ); + UriFree(url); + if (!ctx) + { + return janet_wrap_nil(); + } + + for (i = 0; headers && i < headers->capacity; i++) + { + JanetKV kv = headers->data[i]; + char *key, *value; + if (!janet_checktype(kv.key, JANET_STRING)) + { + continue; + } + + key = (char *) janet_unwrap_string(kv.key); + value = (char *) janet_to_string(kv.value); + HttpRequestHeader(ctx, key, value); + } + if (data) + { + char *len_str = StrInt((int) data->count); + HttpRequestHeader(ctx, "Content-Length", len_str); + Free(len_str); + } + HttpRequestSendHeaders(ctx); + if (data) + { + for (i = 0; i < data->count; i++) + { + uint8_t byte = data->data[i]; + StreamPutc(HttpClientStream(ctx), byte); + } + } + code = HttpRequestSend(ctx); + raw_headers = HttpResponseHeaders(ctx); + + abs = janet_abstract(&janet_http_type, sizeof(HttpStruct)); + http = (HttpStruct *) abs; + http->code = code; + http->headers = HashMapCreate(); + http->size = 0; + http->data = NULL; + while (HashMapIterate(raw_headers, &header, (void **) &value)) + { + HashMapSet(http->headers, header, StrDuplicate(value)); + } + + while ((ch = StreamGetc(HttpClientStream(ctx))) != EOF) + { + http->data = Realloc(http->data, ++(http->size)); + http->data[http->size-1] = ch; + } + HttpClientContextFree(ctx); + + return janet_wrap_abstract(abs); +} + +static int +JanetHttpGet(void *http0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) http0; + return janet_getmethod(kw, http_methods, out); +} +static void +JanetMarshalHttp(void *http0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, http0); +} +static void * +JanetUnmarshalHttp(JanetMarshalContext *ctx) +{ + HttpStruct *http = janet_unmarshal_abstract(ctx, sizeof(HttpStruct)); + return http; +} +static void +JanetHttpToString(void *http0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " HTTP object]"); +} +static int32_t +JanetHashHttp(void *http0, size_t size) +{ + uintptr_t httpp = (uintptr_t) http0; + + (void) size; + return (int32_t) httpp; +} +static Janet +JanetHttpNext(void *http0, Janet next) +{ + (void) http0; + return janet_nextmethod(http_methods, next); +} +static int +JanetCloseHttp(void *data0, size_t size) +{ + HttpStruct *http = data0; + char *key; + void *val; + + while (HashMapIterate(http->headers, &key, &val)) + { + Free(val); + } + HashMapFree(http->headers); + Free(http->data); + return 0; +} +static Janet +JanetHttpCode(int32_t argc, Janet *argv) +{ + JanetAbstract *abs; + HttpStruct *http; + janet_fixarity(argc, 1); + + abs = janet_getabstract(argv, 0, &janet_http_type); + if (!abs) + { + return janet_wrap_nil(); + } + http = (HttpStruct *) abs; + + return janet_wrap_integer(http->code); +} +static Janet +JanetHttpHeaders(int32_t argc, Janet *argv) +{ + return janet_wrap_nil(); +} +static Janet +JanetHttpResponse(int32_t argc, Janet *argv) +{ + JanetBuffer *buffer; + JanetAbstract *abs; + HttpStruct *http; + janet_fixarity(argc, 1); + + abs = janet_getabstract(argv, 0, &janet_http_type); + if (!abs) + { + return janet_wrap_nil(); + } + http = (HttpStruct *) abs; + + buffer = janet_buffer(http->size + 1); + janet_buffer_push_bytes(buffer, (const uint8_t *) http->data, http->size); + + return janet_wrap_buffer(buffer); +} +#endif diff --git a/src/Extensions/Internal.h b/src/Extensions/Internal.h new file mode 100644 index 0000000..db0af88 --- /dev/null +++ b/src/Extensions/Internal.h @@ -0,0 +1,98 @@ +#ifndef PARSEE_IEXTENSIONS_H +#define PARSEE_IEXTENSIONS_H +#ifdef JANET + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#define JANET_STANZA_PUSH 0 +#define JANET_REQUEST_XMPPCMD 1 +#define JANET_REQUEST_XMPPCMD_A 2 +#define JANET_REQUEST_XMPPCMD_F 3 + +struct Extensions { + HashMap *extensions; + size_t count; + + ParseeData *data; + + pthread_mutex_t mtx; +}; +struct Extension { + Extensions *source; + + JanetTable *env; + JanetVM *vm; + char *id; + + uint8_t *bytes; + size_t len; + char *file; + + JanetFiber *stanzas; + + pthread_t thr; + volatile bool halted; + + XMPPCommandManager *manager; + JanetTable *functions; +}; + +/** Creates a Janet table from an XML element. For all sensical + * usecases, set in to NULL. + * ----------------------- + * Returns: a wrapped Janet table[janet] */ +Janet ExtensionsFromXML(JanetArray *in, XMLElement *e); + +/** Takes a Janet table and tries to encode it as XML. + * ---------------- + * Returns: an XML element[HEAP] | NULL */ +XMLElement * ExtensionsToXML(JanetTable *t); + +/** Creates a Janet table from a JSON object. + * ---------- + * Returns: a wrapped Janet table[janet] | NULL */ +Janet JsonToJanet(HashMap *obj); + +/** Takes a Janet table and tries to encode it as JSON. + * ---------------- + * Returns: a JSON object[HEAP] | NULL */ +HashMap * JanetToJson(JanetTable *table); + +/** Configures some basic context for the embedded Janet environment. + * --------------------------- + * Returns: NOTHING + * Modifies: env */ +void SetupBasicExtensionContext(Extension *ext, JanetTable *env); + +/** Tries to get a ParseeData from a Janet value(see 'parsee/ctx'). + * ------------------ + * Returns: the data object | NULL */ +ParseeData * GetParseeData(Janet v); + +/** Generates a Janet value from a ParseeData. + * ------------ + * See-Also; GetParseeData */ +Janet FromParseeData(ParseeData *data); + +bool +TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out); +#endif +#endif diff --git a/src/Extensions/JSON.c b/src/Extensions/JSON.c new file mode 100644 index 0000000..1996b9d --- /dev/null +++ b/src/Extensions/JSON.c @@ -0,0 +1,170 @@ +#ifdef JANET +#include + +#include +#include +#include + +#include +#include + +Janet JsonToJanet(HashMap *obj); +static Janet +JsonValueToJanet(JsonValue *val) +{ + Janet jVal; + switch (JsonValueType(val)) + { + case JSON_STRING: + jVal = janet_cstringv(JsonValueAsString(val)); + break; + case JSON_NULL: + jVal = janet_wrap_nil(); + break; + case JSON_FLOAT: + jVal = janet_wrap_number(JsonValueAsFloat(val)); + break; + case JSON_INTEGER: + jVal = janet_wrap_s64(JsonValueAsInteger(val)); + break; + case JSON_BOOLEAN: + jVal = janet_wrap_boolean(JsonValueAsBoolean(val)); + break; + case JSON_OBJECT: + jVal = JsonToJanet(JsonValueAsObject(val)); + break; + case JSON_ARRAY: + { + JanetArray *arr = janet_array(0); + Array *jArr = JsonValueAsArray(val); + size_t i, len; + for (i = 0, len = ArraySize(jArr); i < len; i++) + { + janet_array_push(arr, JsonValueToJanet(ArrayGet(jArr, i))); + } + jVal = janet_wrap_array(arr); + } + break; + } + + return jVal; +} + +Janet +JsonToJanet(HashMap *obj) +{ + JanetTable *table; + char *key; + JsonValue *val; + if (!obj) + { + return janet_wrap_nil(); + } + + table = janet_table(0); + while (HashMapIterate(obj, &key, (void **) &val)) + { + Janet jVal = JsonValueToJanet(val); + janet_table_put(table, janet_cstringv(key), jVal); + } + + return janet_wrap_table(table); +} + +HashMap * JanetToJson(JanetTable *table); +static JsonValue * +JanetToJsonValue(Janet janet) +{ + JsonValue *ret = NULL; + if (janet_type(janet) == JANET_STRING) + { + ret = JsonValueString((char *) janet_unwrap_string(janet)); + } + else if (janet_type(janet) == JANET_BOOLEAN) + { + ret = JsonValueBoolean(janet_unwrap_boolean(janet)); + } + else if (janet_type(janet) == JANET_NIL) + { + ret = JsonValueNull(); + } + else if (janet_type(janet) == JANET_ARRAY) + { + JanetArray *arr = janet_unwrap_array(janet); + Array *jArr = ArrayCreate(); + size_t i; + + for (i = 0; i < arr->count; i++) + { + Janet child = arr->data[i]; + ArrayAdd(jArr, JanetToJsonValue(child)); + } + + ret = JsonValueArray(jArr); + } + else if (janet_type(janet) == JANET_TABLE) + { + ret = JsonValueObject(JanetToJson(janet_unwrap_table(janet))); + } + else if (janet_is_int(janet)) + { + ret = JsonValueInteger(janet_unwrap_s64(janet)); + } + else if (janet_type(janet) == JANET_NUMBER) + { + ret = JsonValueFloat(janet_unwrap_number(janet)); + } + + return ret; +} +HashMap * +JanetToJson(JanetTable *table) +{ + HashMap *ret; + size_t i; + if (!table) + { + return NULL; + } + + ret = HashMapCreate(); + for (i = 0; i < table->capacity; i++) + { + JanetKV *kv = &table->data[i]; + char *key; + if (!janet_checktype(kv->key, JANET_STRING)) + { + continue; + } + + key = (char *) janet_unwrap_string(kv->key); + + JsonValueFree(HashMapSet(ret, key, JanetToJsonValue(kv->value))); + } + + return ret; +} + +Janet +JanetParseJSON(int32_t argc, Janet *argv) +{ + JanetBuffer *buf = NULL; + Stream *buffer_stream; + HashMap *json; + Janet ret; + + janet_fixarity(argc, 1); + if (!(buf = janet_getbuffer(argv, 0))) + { + return janet_wrap_nil(); + } + + buffer_stream = StrStreamReaderN((char *) buf->data, buf->count); + json = JsonDecode(buffer_stream); + StreamClose(buffer_stream); + ret = JsonToJanet(json); + JsonFree(json); + + return ret; +} +#endif diff --git a/src/Extensions/Jabber.c b/src/Extensions/Jabber.c new file mode 100644 index 0000000..9adcaec --- /dev/null +++ b/src/Extensions/Jabber.c @@ -0,0 +1,30 @@ +#ifdef JANET +#include "Extensions/Internal.h" + +#include "Extensions/Functions.h" + +Janet +JanetWriteStanza(int32_t argc, Janet *argv) +{ + XMLElement *xml; + ParseeData *data; + + janet_fixarity(argc, 2); + if (janet_type(argv[0]) != JANET_POINTER) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_TABLE) + { + return janet_wrap_nil(); + } + + data = janet_unwrap_pointer(argv[0]); + xml = ExtensionsToXML(janet_unwrap_table(argv[1])); + + XMPPSendStanza(data->jabber, xml, data->config->max_stanza_size); + + XMLFreeElement(xml); + return janet_wrap_nil(); +} +#endif diff --git a/src/Extensions/Janet.c b/src/Extensions/Janet.c new file mode 100644 index 0000000..0442688 --- /dev/null +++ b/src/Extensions/Janet.c @@ -0,0 +1,304 @@ +#ifdef JANET +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Extensions/Internal.h" + +bool +ExtensionEnabled(void) +{ + return true; +} + +Extensions * +ExtensionCreateContext(ParseeData *data) +{ + Extensions *ctx = Malloc(sizeof(*ctx)); + + ctx->extensions = HashMapCreate(); + ctx->data = data; + ctx->count = 0; + + pthread_mutex_init(&ctx->mtx, NULL); + return ctx; +} +void +ExtensionDestroyContext(Extensions *ctx) +{ + char *key; + Extension *ext; + if (!ctx) + { + return; + } + + while (HashMapIterate(ctx->extensions, &key, (void **) &ext)) + { + /* TODO */ + ext->halted = true; + janet_loop1_interrupt(ext->vm); + + pthread_join(ext->thr, NULL); + XMPPFreeManager(ext->manager); + Free(ext->bytes); + Free(ext->file); + Free(ext->id); + Free(ext); + } + pthread_mutex_destroy(&ctx->mtx); + HashMapFree(ctx->extensions); + Free(ctx); +} + +Janet +GetGlobal(Extension *ext, char *key) +{ + Janet v; + + v = janet_table_get(ext->env, janet_csymbolv(key)); + if (janet_type(v) == JANET_NIL) + { + return janet_wrap_nil(); + } + return janet_table_get(janet_unwrap_table(v), janet_ckeywordv("value")); +} +bool +TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out) +{ + Janet v = GetGlobal(ext, func); + JanetFiber *fib = NULL; + JanetSignal sig = JANET_SIGNAL_OK; + if (janet_type(v) == JANET_FUNCTION) + { + sig = janet_pcall( + janet_unwrap_function(v), + argc, argv, + out, &fib + ); + } + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, *out); + } + + return sig == JANET_SIGNAL_OK; +} + +static void * +ExtensionLoop(void *p) +{ + Extension *ext = p; + Janet f_table; + + janet_init(); + ext->env = janet_core_env(NULL); + ext->functions = janet_table(0); + ext->vm = janet_local_vm(); + + f_table = janet_wrap_table(ext->functions); + janet_gcroot(f_table); + + /* Load the Janet environment */ + SetupBasicExtensionContext(ext, ext->env); + janet_dobytes(ext->env, ext->bytes, ext->len, ext->file, NULL); + + while (!ext->halted) + { + janet_loop(); + UtilSleepMillis(1); + } + + janet_gcunroot(f_table); + + //janet_table_deinit(ext->env); + janet_deinit(); + return NULL; +} + +void +ExtensionReload(Extensions *ctx, char *id) +{ + Extension *ext; + Log(LOG_INFO, "ctx=%p id=%s", ctx, id); + if (!ctx || !id) + { + return; + } + + pthread_mutex_lock(&ctx->mtx); + Log(LOG_INFO, "Reloading %s", id); + ext = HashMapGet(ctx->extensions, id); + if (!ext) + { + pthread_mutex_unlock(&ctx->mtx); + Log(LOG_INFO, ":("); + return; + } + ext->halted = true; + janet_loop1_interrupt(ext->vm); + + pthread_join(ext->thr, NULL); + XMPPFreeManager(ext->manager); + Free(ext->bytes); + Free(ext->id); + HashMapDelete(ctx->extensions, id); + + ExtensionLoadBasic(ctx, id, ext->file); + Free(ext->file); + Free(ext); + pthread_mutex_unlock(&ctx->mtx); +} +Extension * +ExtensionLoadBasic(Extensions *ctx, char *id, char *file) +{ + Extension *ext; + Stream *stream; + uint8_t *bytes = NULL; + size_t len = 0; + int c; + + if (!ctx || !id || !file) + { + return NULL; + } + + if (!(stream = StreamOpen(file, "r"))) + { + return NULL; + } + while ((c = StreamGetc(stream)) != EOF) + { + bytes = Realloc(bytes, len + 1); + bytes[len++] = c; + } + StreamClose(stream); + + ext = Malloc(sizeof(*ext)); + + /* will be set by the thread */ + ext->vm = NULL; + ext->stanzas = NULL; + + ext->file = StrDuplicate(file); + ext->id = StrDuplicate(id); + ext->bytes = bytes; + ext->source = ctx; + ext->len = len; + ext->halted = false; + ext->manager = XMPPCreateManager(ext); + HashMapSet(ctx->extensions, id, ext); + + pthread_create( + &ext->thr, + NULL, + ExtensionLoop, + ext + ); + return ext; +} + +static bool +EndsWith(char *str, const char *ext) +{ + size_t len_str, len_ext; + if (!str || !ext) + { + return false; + } + + len_str = strlen(str); + len_ext = strlen(ext); + if (len_ext > len_str) + { + return false; + } + + return StrEquals(str + (len_str - len_ext), ext); +} +void +ExtensionLoadDir(Extensions *ctx, char *dir) +{ + struct dirent *ent; + DIR *handle; + if (!ctx || !dir) + { + return; + } + if (!(handle = opendir(dir))) + { + return; + } + while ((ent = readdir(handle))) + { + const char *extension = ".janet"; + char *name = ent->d_name; + char *actual_path; + char *id = name; + if (!EndsWith(name, extension)) + { + continue; + } + id = StrDuplicate(id); + actual_path = StrConcat(3, dir, "/", name); + id[strlen(name) - strlen(extension)] = '\0'; + + ExtensionLoadBasic(ctx, id, actual_path); + + Free(actual_path); + Free(id); + } + + closedir(handle); +} +void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza) +{ + size_t i; + char *key; + Extension *val; + if (!ctx || !jid || !query || !stanza) + { + return; + } + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + XMPPShoveCommandList(val->manager, jid, query, stanza); + } + pthread_mutex_unlock(&ctx->mtx); +} +void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza) +{ + size_t i; + char *key; + Extension *val; + if (!ctx || !stanza) + { + return; + } + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + XMPPManageCommand(val->manager, stanza, ctx->data); + } + pthread_mutex_unlock(&ctx->mtx); +} +#endif diff --git a/src/Extensions/KV.c b/src/Extensions/KV.c new file mode 100644 index 0000000..69819c5 --- /dev/null +++ b/src/Extensions/KV.c @@ -0,0 +1,86 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +Janet +JanetGetKey(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *chat_id = NULL; + char *key = NULL, *val = NULL; + Janet ret; + + janet_fixarity(argc, 3); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string")); + return janet_wrap_nil(); + } + + if (!(data = janet_unwrap_pointer(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is NULL..")); + return janet_wrap_nil(); + } + chat_id = (char *) janet_unwrap_string(argv[1]); + key = (char *) janet_unwrap_string(argv[2]); + + if ((val = ParseeGetChatSetting(data, chat_id, key))) + { + ret = janet_cstringv(val); + Free(val); + } + else + { + ret = janet_wrap_nil(); + } + return ret; +} +Janet +JanetSetKey(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *chat_id = NULL; + char *key = NULL, *val = NULL; + + janet_fixarity(argc, 4); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[3], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("VAL is not a string")); + return janet_wrap_nil(); + } + + chat_id = (char *) janet_unwrap_string(argv[1]); + key = (char *) janet_unwrap_string(argv[2]); + val = (char *) janet_unwrap_string(argv[3]); + + ParseeSetChatSetting(data, chat_id, key, val); + return janet_wrap_nil(); +} +#endif + diff --git a/src/Extensions/Matrix.c b/src/Extensions/Matrix.c new file mode 100644 index 0000000..e0d33a8 --- /dev/null +++ b/src/Extensions/Matrix.c @@ -0,0 +1,149 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +#include + +Janet +JanetMatrixInvite(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *invitee, *room; + janet_fixarity(argc, 3); + + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + return janet_wrap_nil(); + } + + invitee = (char *) janet_unwrap_string(argv[1]); + room = (char *) janet_unwrap_string(argv[2]); + if (!data) + { + return janet_wrap_nil(); + } + + ASInvite(data->config, room, invitee); + return janet_wrap_nil(); +} +Janet +JanetMatrixFind(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *id, *room; + Janet returned; + HashMap *e; + janet_fixarity(argc, 3); + + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + return janet_wrap_nil(); + } + + room = (char *) janet_unwrap_string(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + if (!data) + { + return janet_wrap_nil(); + } + + if (!(e = ASFind(data->config, room, id))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("couldnt find event")); + return janet_wrap_nil(); + } + returned = JsonToJanet(e); + JsonFree(e); + return returned; +} +Janet +JanetMatrixSend(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *from, *room, *type, *id; + JanetTable *json; + Janet returned; + HashMap *e; + janet_fixarity(argc, 5); + + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("FROM is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("ROOM is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[3]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("TYPE is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[4]) != JANET_TABLE) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("JSON is not a table.") + ); + return janet_wrap_nil(); + } + + from = (char *) janet_unwrap_string(argv[1]); + room = (char *) janet_unwrap_string(argv[2]); + type = (char *) janet_unwrap_string(argv[3]); + json = janet_unwrap_table(argv[4]); + if (!data) + { + return janet_wrap_nil(); + } + + if (!(e = JanetToJson(json))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("JSON is not valid JSON.") + ); + return janet_wrap_nil(); + } + + if (!(id = ASSend(data->config, room, from, type, e, 0))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Could not send event") + ); + return janet_wrap_nil(); + } + returned = janet_cstringv(id); + Free(id); + return returned; +} +#endif diff --git a/src/Extensions/PushEvents.c b/src/Extensions/PushEvents.c new file mode 100644 index 0000000..40ae858 --- /dev/null +++ b/src/Extensions/PushEvents.c @@ -0,0 +1,89 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +typedef struct ExtensionEventReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + HashMap *obj; + + Extension *ext; + bool reply, ack; +} ExtensionEventReply; + +static void +OnEventResponse(JanetEVGenericMessage msg) +{ + ExtensionEventReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet args[1] = { + JsonToJanet(reply->obj), + }; + Janet ret; + + if (TryCall(ext, "on-event", 1, args, &ret)) + { + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = janet_truthy(ret); + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); + return; + } + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = false; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} +bool +ExtensionPushEvent(Extensions *ctx, HashMap *obj) +{ + bool veto = false; + size_t i; + + char *key; + Extension *val; + if (!ctx || !obj) + { + return false; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionEventReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + /* We except a veto boolean. */ + .tag = JANET_STANZA_PUSH, + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->reply = false; + reply->ack = false; + reply->obj = obj; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnEventResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + if (reply->reply) + { + veto = true; + } + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); + + return veto; +} +#endif diff --git a/src/Extensions/PushStanza.c b/src/Extensions/PushStanza.c new file mode 100644 index 0000000..4f61961 --- /dev/null +++ b/src/Extensions/PushStanza.c @@ -0,0 +1,98 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +typedef struct ExtensionStanzaReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + XMLElement *stanza; + + Extension *ext; + bool reply, ack; + char *type; +} ExtensionStanzaReply; + +static void +OnStanzaResponse(JanetEVGenericMessage msg) +{ + ExtensionStanzaReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet args[2] = { + ExtensionsFromXML(NULL, reply->stanza), + janet_cstringv(reply->type) + }; + Janet ret; + + if (TryCall(ext, "on-stanza", 2, args, &ret)) + { + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = janet_truthy(ret); + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); + return; + } + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = false; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} +bool +ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t) +{ + bool veto = false; + size_t i; + + char *table[STANZA_TYPE_COUNT] = { + "raw", + "message", + "iq", + "presence" + }; + char *key; + Extension *val; + if (!ctx || !stanza) + { + return false; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionStanzaReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + /* We except a veto boolean. */ + .tag = JANET_STANZA_PUSH, + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->reply = false; + reply->ack = false; + reply->type = table[t]; + reply->stanza = stanza; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnStanzaResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + if (reply->reply) + { + veto = true; + } + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); + + return veto; +} +#endif diff --git a/src/Extensions/RequestCmds.c b/src/Extensions/RequestCmds.c new file mode 100644 index 0000000..61a1bca --- /dev/null +++ b/src/Extensions/RequestCmds.c @@ -0,0 +1,580 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +#include +#include + +typedef struct ExtensionCommandReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + XMLElement *form; + char *from; + char *node; + + XMLElement *out; + + XMPPCommand *cmd; + + Extension *ext; + bool ack; +} ExtensionCommandReply; + +static void OnCommandResponse(JanetEVGenericMessage msg); + +static int JanetCmdGet(void *data0, Janet key, Janet *out); +static void JanetMarshalCmd(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalCmd(JanetMarshalContext *ctx); +static void JanetCmdToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashCmd(void *data0, size_t size); +static Janet JanetCmdNext(void *data0, Janet next); + +static Janet JanetCmdCreateText(int32_t argc, Janet *argv); +static Janet JanetCmdCreateBool(int32_t argc, Janet *argv); +static Janet JanetCmdCreateFixed(int32_t argc, Janet *argv); +static Janet JanetCmdSetTitle(int32_t argc, Janet *argv); +static const JanetMethod cmd_methods[] = { + { "create-text", JanetCmdCreateText }, + { "create-bool", JanetCmdCreateBool }, + { "create-fixed", JanetCmdCreateFixed }, + { "set-title", JanetCmdSetTitle }, + { NULL, NULL } +}; +const JanetAbstractType janet_cmd_type = { + .name = "parsee-xmppcmd", + .gc = NULL, .gcmark = NULL, + + .get = JanetCmdGet, .put = NULL, + .marshal = JanetMarshalCmd, .unmarshal = JanetUnmarshalCmd, + .tostring = JanetCmdToString, + + .compare = NULL, + .hash = JanetHashCmd, + + .next = JanetCmdNext, + JANET_ATEND_NEXT +}; + + +static XMPPCommand * +JanetToCommand(Janet v) +{ + XMPPCommand **indirect = janet_checkabstract(v, &janet_cmd_type); + return indirect ? *indirect : NULL; +} +static Janet +CommandToJanet(XMPPCommand *cmd) +{ + JanetAbstract abs; + if (!cmd) + { + return janet_wrap_nil(); + } + + abs = janet_abstract(&janet_cmd_type, sizeof(XMPPCommand *)); + *((XMPPCommand **) abs) = cmd; + return janet_wrap_abstract(abs); +} + +static void +PutFunction(JanetTable *functions, char *node, char *kind, JanetFunction *f) +{ + JanetTable *kinds; + Janet kindsv; + if (!functions || !node || !kind || !f) + { + return; + } + + kindsv = janet_table_get(functions, janet_cstringv(node)); + if (janet_checktype(kindsv, JANET_NIL)) + { + kinds = janet_table(0); + janet_table_put( + functions, + janet_cstringv(node), janet_wrap_table(kinds) + ); + } + else + { + kinds = janet_unwrap_table(kindsv); + } + + janet_table_put(kinds, janet_cstringv(kind), janet_wrap_function(f)); + +} +static JanetFunction * +FetchFunction(JanetTable *functions, char *node, char *kind) +{ + Janet f_tablev, fv; + JanetTable *ftable; + if (!functions || !node || !kind) + { + return NULL; + } + + f_tablev = janet_table_get( + functions, + janet_cstringv(node) + ); + if (!janet_checktype(f_tablev, JANET_TABLE)) + { + return NULL; + } + ftable = janet_unwrap_table(f_tablev); + + fv = janet_table_get( + ftable, + janet_cstringv(kind) + ); + if (!janet_checktype(fv, JANET_FUNCTION)) + { + return NULL; + } + + return janet_unwrap_function(fv); +} + +extern void +JanetFormCb(XMPPCommandManager *manager, XMPPCommand *cmd, char *from, char *node) +{ + Extension *ext = XMPPGetManagerCookie(manager); + ExtensionCommandReply *reply; + + reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD_F + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = ext; + reply->node = node; + reply->from = from; + reply->ack = false; + reply->cmd = cmd; + msg.argp = reply; + + janet_ev_post_event(ext->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); + +} +static void +JanetCmdCb(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) +{ + Extension *ext = XMPPGetManagerCookie(m); + ExtensionCommandReply *reply; + + reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD_A + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = ext; + reply->node = node; + reply->form = form; + reply->from = from; + reply->ack = false; + reply->out = out; + msg.argp = reply; + + janet_ev_post_event(ext->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); +} +static Janet +JanetRegisterCommand(int32_t argc, Janet *argv) +{ + JanetTable *self; + JanetFunction *func; + JanetFunction *form; + char *node, *name; + Janet v; + + Extension *ext; + XMPPCommand *cmd; + + janet_arity(argc, 4, 5); + + if (!janet_checktype(argv[0], JANET_TABLE) || + !janet_checktype(argv[1], JANET_STRING) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_FUNCTION) || + (argc == 5 && !janet_checktype(argv[4], JANET_FUNCTION))) + { + return janet_wrap_nil(); + } + self = janet_unwrap_table(argv[0]); + node = (char *) janet_unwrap_string(argv[1]); + name = (char *) janet_unwrap_string(argv[2]); + func = janet_unwrap_function(argv[3]); + form = argc == 5 ? janet_unwrap_function(argv[4]) : NULL; + + v = janet_table_get(self, janet_ckeywordv("ptr")); + if (!janet_checktype(v, JANET_POINTER)) + { + return janet_wrap_nil(); + } + ext = janet_unwrap_pointer(v); + + PutFunction(ext->functions, node, "act", func); + PutFunction(ext->functions, node, "form", form); + cmd = XMPPBasicCmd(XMPPCMD_ADMINS, node, name, JanetCmdCb); + if (form) + { + XMPPCmdOptionsCreator(cmd, JanetFormCb); + } + XMPPRegisterCommand(ext->manager, cmd); + + return janet_wrap_nil(); +} + +static void +OnCommandResponse(JanetEVGenericMessage msg) +{ + ExtensionCommandReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet ret, in; + + if (msg.tag == JANET_REQUEST_XMPPCMD) + { + JanetTable *cmd = janet_table(0); + janet_table_put(cmd, + janet_ckeywordv("ptr"), + janet_wrap_pointer(reply->ext) + ); + janet_table_put(cmd, + janet_ckeywordv("register"), + janet_wrap_cfunction(JanetRegisterCommand) + ); + in = janet_wrap_table(cmd); + + TryCall(ext, "on-xmppcmd", 1, &in, &ret); + } + else if (msg.tag == JANET_REQUEST_XMPPCMD_F) + { + Janet in[2] = { + janet_cstringv(reply->from), + CommandToJanet(reply->cmd) + }; + JanetFiber *fib = NULL; + JanetFunction *func; + JanetSignal sig; + + func = FetchFunction(reply->ext->functions, reply->node, "form"); + if (func) + { + sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib); + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, ret); + } + } + } + else if (msg.tag == JANET_REQUEST_XMPPCMD_A) + { + JanetFunction *func; + + func = FetchFunction(reply->ext->functions, reply->node, "act"); + if (func) + { + Janet in[2] = { + janet_cstringv(reply->from), + ExtensionsFromXML(NULL, reply->form) + }; + JanetFiber *fib = NULL; + JanetSignal sig; + XMLElement *out = reply->out; + sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib); + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, ret); + SetNote("error", "Internal error in Janet extension."); + } + + if (janet_checktype(ret, JANET_ARRAY)) + { + JanetArray *arr = janet_unwrap_array(ret); + size_t i, added; + for (i = 0, added = 0; i < arr->count; i++) + { + Janet v = arr->data[i]; + JanetTable *source; + XMLElement *child = NULL; + + if (!reply->out || !janet_checktype(v, JANET_TABLE)) + { + continue; + } + source = janet_unwrap_table(v); + child = ExtensionsToXML(source); + XMLAddChild(reply->out, child); + if (child) + { + added++; + } + } + if (added == 0) + { + SetNote("error", "No values returned in Janet extension."); + } + } + else + { + SetNote("error", "Invalid output in Janet extension."); + } + } + } + + pthread_mutex_lock(&reply->lock); + reply->ack = true; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} + +void +ExtensionRequestCommands(Extensions *ctx) +{ + size_t i; + char *key; + Extension *val; + if (!ctx) + { + return; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionCommandReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->ack = false; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); +} + +static int +JanetCmdGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, cmd_methods, out); +} +static void +JanetMarshalCmd(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalCmd(JanetMarshalContext *ctx) +{ + XMPPCommand **data = janet_unmarshal_abstract(ctx, sizeof(XMPPCommand *)); + return data ? *data : NULL; +} +static void +JanetCmdToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " XMPP command type]"); +} +static int32_t +JanetHashCmd(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* We're casting down, but in a context where there is really + * just one ParseeData, is that really such a crime? */ + return (int32_t) datap; +} +static Janet +JanetCmdNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(cmd_methods, next); +} + + +static Janet +JanetCmdCreateText(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + bool req; + char *id, *def, *desc; + + janet_arity(argc, 4, 5); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_BOOLEAN) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_STRING) || + ((argc == 5) && !janet_checktype(argv[4], JANET_STRING))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + req = janet_unwrap_boolean(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + def = (char *) janet_unwrap_string(argv[3]); + desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL; + + opt = XMPPCreateText(req, id, def); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} + +static Janet +JanetCmdCreateBool(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + bool req, def; + char *id, *desc; + + janet_arity(argc, 4, 5); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_BOOLEAN) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_BOOLEAN) || + ((argc == 5) && !janet_checktype(argv[4], JANET_STRING))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + req = janet_unwrap_boolean(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + def = janet_unwrap_boolean(argv[3]); + desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL; + + opt = XMPPCreateBool(req, id, def); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} +static Janet +JanetCmdCreateFixed(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + char *id, *desc; + + janet_fixarity(argc, 3); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING) || + !janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + id = (char *) janet_unwrap_string(argv[1]); + desc = (char *) janet_unwrap_string(argv[2]); + + opt = XMPPCreateFixed(id, desc); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} +static Janet +JanetCmdSetTitle(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + + char *title; + + janet_fixarity(argc, 2); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + title = (char *) janet_unwrap_string(argv[1]); + + XMPPSetFormTitle(cmd, title); + return janet_wrap_nil(); +} +#endif diff --git a/src/Extensions/StanzaInfo.c b/src/Extensions/StanzaInfo.c new file mode 100644 index 0000000..1400aaa --- /dev/null +++ b/src/Extensions/StanzaInfo.c @@ -0,0 +1,98 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +Janet +JanetStanzaInfo(int32_t argc, Janet *argv) +{ + ParseeData *data; + XMLElement *stanza; + + JanetTable *ret; + + char *trimmed = NULL; + char *chat_id = NULL; + char *from = NULL; + char *room = NULL; + char *mxid = NULL; + + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_TABLE)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not a table")); + return janet_wrap_nil(); + } + + if (!(stanza = ExtensionsToXML(janet_unwrap_table(argv[1])))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not XML.")); + return janet_wrap_nil(); + } + + from = HashMapGet(stanza->attrs, "from"); + trimmed = ParseeTrimJID(from); + chat_id = ParseeGetFromMUCID(data, trimmed); + + ret = janet_table(0); + janet_table_put(ret, + janet_ckeywordv("in-muc"), + janet_wrap_boolean(ParseeIsMUC(data, trimmed)) + ); + janet_table_put(ret, + janet_ckeywordv("in-dm"), + janet_wrap_boolean(StrEquals(HashMapGet(stanza->attrs, "type"), "chat")) + ); + if (chat_id && trimmed) + { + room = ParseeGetRoomID(data, chat_id); + janet_table_put(ret, + janet_ckeywordv("muc-jid"), janet_cstringv(trimmed) + ); + janet_table_put(ret, + janet_ckeywordv("id"), janet_cstringv(chat_id) + ); + } + else + { + char *to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); + char *dmid = ParseeGetDMID(to, from); + + room = ParseeFindDMRoom(data, to, from); + if (dmid) + { + janet_table_put(ret, + janet_ckeywordv("id"), janet_cstringv(dmid) + ); + } + + Free(dmid); + Free(to); + } + + if (room) + { + janet_table_put(ret, + janet_ckeywordv("room-id"), janet_cstringv(room) + ); + } + + if ((mxid = ParseeGetBridgedUser(data, stanza))) + { + janet_table_put(ret, + janet_ckeywordv("bridged-sender"), janet_cstringv(mxid) + ); + } + + Free(trimmed); + Free(chat_id); + Free(room); + Free(mxid); + XMLFreeElement(stanza); + return janet_wrap_table(ret); +} +#endif diff --git a/src/Extensions/XML.c b/src/Extensions/XML.c new file mode 100644 index 0000000..2567f82 --- /dev/null +++ b/src/Extensions/XML.c @@ -0,0 +1,357 @@ +#ifdef JANET +#include + +#include +#include + +#include +#include + +static Janet XMLToString(int32_t argc, Janet *argv); +static Janet JanetXMLAddAttr(int32_t argc, Janet *argv); +static void InitialiseBasicXML(JanetTable *table, char *type); + +XMLElement * +ExtensionsToXML(JanetTable *t) +{ + XMLElement *ret = NULL; + + char *type = NULL, *name, *data; + JanetArray *children; + JanetTable *attrs; + int32_t i; + Janet v; + + if (!t) + { + return NULL; + } +#define ChkType(ent, type) (janet_type((v = janet_table_get(t, janet_ckeywordv(ent)))) == type) + if (!ChkType("xml-type", JANET_STRING)) + { + return NULL; + } + type = (char *) janet_unwrap_string(v); + if (StrEquals(type, "data")) + { + if (!ChkType("xml-data", JANET_STRING)) + { + return NULL; + } + data = (char *) janet_unwrap_string(v); + + ret = XMLCreateText(data); + } + else if (StrEquals(type, "tag")) + { + if (!ChkType("xml-name", JANET_STRING)) + { + return NULL; + } + name = (char *) janet_unwrap_string(v); + + if (!ChkType("xml-attrs", JANET_TABLE)) + { + return NULL; + } + + ret = XMLCreateTag(name); + attrs = janet_unwrap_table(v); + for (i = 0; i < attrs->capacity; i++) + { + JanetKV *kv = &attrs->data[i]; + if (!janet_checktype(kv->key, JANET_STRING) || + !janet_checktype(kv->value, JANET_STRING)) + { + continue; + } + XMLAddAttr(ret, + (char *) janet_unwrap_string(kv->key), + (char *) janet_unwrap_string(kv->value) + ); + } + + if (!ChkType("xml-children", JANET_ARRAY)) + { + return ret; + } + children = janet_unwrap_array(v); + + for (i = 0; i < children->capacity; i++) + { + Janet cJanet = children->data[i]; + XMLElement *cXML = NULL; + if (!janet_checktype(cJanet, JANET_TABLE)) + { + continue; + } + + cXML = ExtensionsToXML(janet_unwrap_table(cJanet)); + XMLAddChild(ret, cXML); + } + } + + return ret; +} +Janet +ExtensionsFromXML(JanetArray *in, XMLElement *e) +{ + JanetArray *children = NULL; + JanetTable *table; + size_t i, len; + char *type = NULL; + if (!e) + { + return janet_wrap_nil(); + } + + table = janet_table(0); + switch (e->type) + { + case XML_ELEMENT_TAG: + type = "tag"; + break; + case XML_ELEMENT_DATA: + case XML_ELEMENT_CDATA: + type = "data"; + break; + case XML_ELEMENT_PI: + type = "pi"; + break; + case XML_ELEMENT_UNKNOWN: + type = "unknown"; + break; + } + + InitialiseBasicXML(table, type); + if (e->type == XML_ELEMENT_DATA || e->type == XML_ELEMENT_PI) + { + janet_table_put(table, + janet_ckeywordv("xml-data"), janet_cstringv(e->data) + ); + } + else if (e->type == XML_ELEMENT_TAG) + { + char *attr, *val; + JanetTable *attrs; + children = janet_array(ArraySize(e->children)); + janet_table_put(table, + janet_ckeywordv("xml-name"), janet_cstringv(e->name) + ); + for (i = 0, len = ArraySize(e->children); i < len; i++) + { + ExtensionsFromXML(children, ArrayGet(e->children, i)); + } + janet_table_put(table, + janet_ckeywordv("xml-children"), janet_wrap_array(children) + ); + + attrs = janet_table(0); + while (HashMapIterate(e->attrs, &attr, (void **) &val)) + { + janet_table_put(attrs, + janet_cstringv(attr), janet_cstringv(val) + ); + } + janet_table_put(table, + janet_ckeywordv("xml-attrs"), janet_wrap_table(attrs) + ); + } + if (children && in) + { + janet_array_push(in, janet_wrap_table(table)); + } + return janet_wrap_table(table); +} +static Janet +JanetXMLAddAttr(int32_t argc, Janet *argv) +{ + Janet self_v, key_v, val_v, attr_v; + JanetTable *self, *attrs; + char *key, *val; + + janet_fixarity(argc, 3); + + if (!janet_checktype((self_v = argv[0]), JANET_TABLE)) + { + return janet_wrap_nil(); + } + if (!janet_checktype((key_v = argv[1]), JANET_STRING)) + { + return janet_wrap_nil(); + } + if (!janet_checktype((val_v = argv[2]), JANET_STRING)) + { + return janet_wrap_nil(); + } + key = (char *) janet_unwrap_string(key_v); + val = (char *) janet_unwrap_string(val_v); + + self = janet_unwrap_table(self_v); + attr_v = janet_table_get(self, janet_ckeywordv("xml-attrs")); + if (!janet_checktype(attr_v, JANET_TABLE)) + { + return janet_wrap_nil(); + } + attrs = janet_unwrap_table(attr_v); + + janet_table_put(attrs, janet_cstringv(key), janet_cstringv(val)); + + return janet_wrap_nil(); +} +static Janet +XMLToString(int32_t argc, Janet *argv) +{ + XMLElement *xml; + Stream *fake; + Janet ret; + char *buf = NULL; + janet_fixarity(argc, 1); + + if (!janet_checktype(argv[0], JANET_TABLE)) + { + return janet_wrap_nil(); + } + xml = ExtensionsToXML(janet_gettable(argv, 0)); + if (!xml) + { + return janet_wrap_nil(); + } + + fake = StrStreamWriter(&buf); + XMLEncode(fake, xml); + StreamFlush(fake); + StreamClose(fake); + XMLFreeElement(xml); + + ret = janet_cstringv(buf); + Free(buf); + return ret; +} + +Janet +JanetCreateXMLData(int32_t argc, Janet *argv) +{ + Janet parent_v, text_v; + JanetTable *parent; + JanetTable *ret; + char *text; + + janet_arity(argc, 1, 2); + if (argc == 1) + { + text_v = argv[0]; + parent_v = janet_wrap_nil(); + } + else + { + parent_v = argv[0]; + text_v = argv[1]; + } + + if (janet_type(text_v) != JANET_STRING) + { + return janet_wrap_nil(); + } + + text = (char *) janet_unwrap_string(text_v); + ret = janet_table(0); + InitialiseBasicXML(ret, "data"); + janet_table_put(ret, + janet_ckeywordv("xml-data"), janet_cstringv(text) + ); + + if (janet_type(parent_v) == JANET_TABLE) + { + JanetArray *children; + Janet children_v; + + parent = janet_unwrap_table(parent_v); + children_v = janet_table_get(parent, janet_ckeywordv("xml-children")); + if (janet_type(children_v) != JANET_ARRAY) + { + goto end; + } + children = janet_unwrap_array(children_v); + + janet_array_push(children, janet_wrap_table(ret)); + } + +end: + return janet_wrap_table(ret); +} +Janet +JanetCreateXMLTag(int32_t argc, Janet *argv) +{ + Janet parent_v, text_v; + JanetTable *parent; + JanetTable *ret; + char *text; + + janet_arity(argc, 1, 2); + if (argc == 1) + { + text_v = argv[0]; + parent_v = janet_wrap_nil(); + } + else + { + parent_v = argv[0]; + text_v = argv[1]; + } + + if (janet_type(text_v) != JANET_STRING) + { + return janet_wrap_nil(); + } + + text = (char *) janet_unwrap_string(text_v); + ret = janet_table(0); + + InitialiseBasicXML(ret, "tag"); + janet_table_put(ret, + janet_ckeywordv("xml-name"), janet_cstringv(text) + ); + + if (janet_type(parent_v) == JANET_TABLE) + { + JanetArray *children; + Janet children_v; + + parent = janet_unwrap_table(parent_v); + children_v = janet_table_get(parent, janet_ckeywordv("xml-children")); + if (janet_type(children_v) != JANET_ARRAY) + { + goto end; + } + children = janet_unwrap_array(children_v); + + janet_array_push(children, janet_wrap_table(ret)); + } + +end: + return janet_wrap_table(ret); +} +void +InitialiseBasicXML(JanetTable *table, char *type) +{ + janet_table_put(table, + janet_ckeywordv("stringify"), janet_wrap_cfunction(XMLToString) + ); + janet_table_put(table, + janet_ckeywordv("set-attr"), janet_wrap_cfunction(JanetXMLAddAttr) + ); + janet_table_put(table, + janet_ckeywordv("xml-type"), janet_cstringv(type) + ); + if (StrEquals(type, "tag")) + { + janet_table_put(table, + janet_ckeywordv("xml-attrs"), janet_wrap_table(janet_table(0)) + ); + janet_table_put(table, + janet_ckeywordv("xml-children"), janet_wrap_array(janet_array(0)) + ); + } +} +#endif diff --git a/src/HttParsee.c b/src/HttParsee.c index 724f9ff..c47728b 100644 --- a/src/HttParsee.c +++ b/src/HttParsee.c @@ -22,6 +22,9 @@ ParseeRequest(HttpServerContext *ctx, void *argp) /* Basic headers */ HttpResponseStatus(ctx, HTTP_OK); HttpResponseHeader(ctx, "Server", NAME "/v" VERSION); +#ifdef PLATFORM_WINDOWS + HttpResponseHeader(ctx, "X-Powered-By", "some weirdows"); +#endif HttpResponseHeader(ctx, "Connection", "close"); arg.data = data; diff --git a/src/Main.c b/src/Main.c index cebb055..7d26cbe 100644 --- a/src/Main.c +++ b/src/Main.c @@ -18,6 +18,11 @@ #include #include +#ifdef JANET + #include +#endif +#include + static HttpServer *server = NULL; static pthread_t xmpp_thr; static XMPPComponent *jabber = NULL; @@ -71,6 +76,8 @@ Main(Array *args, HashMap *env) int http = 8; int verbose = 0; + Extensions *ext_ctx = NULL; + start = UtilTsMillis(); memset(&conf, 0, sizeof(conf)); @@ -82,6 +89,9 @@ Main(Array *args, HashMap *env) Log(LOG_INFO, "======================="); Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors"); Log(LOG_INFO, "(This program is free software, see LICENSE.)"); +#ifdef JANET + Log(LOG_INFO, "**Built with Janet!**"); +#endif #ifdef PLATFORM_IPHONE Log(LOG_WARNING, "Wait. Are you running this on an iPhone?"); @@ -241,6 +251,10 @@ Main(Array *args, HashMap *env) Log(LOG_DEBUG, "Verbosity level: %d", verbose); ((ParseeData *) conf.handlerArgs)->verbosity = verbose; + ext_ctx = ExtensionCreateContext(conf.handlerArgs); + ((ParseeData *) conf.handlerArgs)->exts = ext_ctx; + ExtensionLoadDir(ext_ctx, parsee_conf->extensions); + Log(LOG_NOTICE, "Setting up local Matrix user..."); if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) { @@ -303,6 +317,8 @@ end: ParseeDestroyHeadTable(); ParseeDestroyJIDTable(); + ExtensionDestroyContext(ext_ctx); + (void) env; return 0; } diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 0898063..46b37fa 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -63,6 +63,8 @@ ParseeConfigLoad(char *conf) CopyToStr(component_host, "component_host"); CopyToStr(shared_comp_secret, "shared_secret"); CopyToInt(max_stanza_size, "max_stanza_size"); + + CopyToStr(extensions, "extensions"); if (!config->max_stanza_size) { /* Standard XMPP "minimum" maximum */ @@ -140,6 +142,7 @@ ParseeConfigFree(void) Free(config->namespace_base); Free(config->media_base); Free(config->listen_as); + Free(config->extensions); Free(config); } const ParseeConfig * diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 46a31ee..f7c8632 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -725,3 +725,13 @@ ParseeIsMediaEnabled(ParseeData *data, char *chat_id) return ret; } +bool +ParseeIsMUC(ParseeData *data, char *jid) +{ + if (!data) + { + return false; + } + + return XMPPQueryMUC(data->jabber, jid, NULL); +} diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index da6194f..ea97b2c 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -153,7 +153,6 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = HashMapGet(elem->attrs, "href"); /* TODO: Check if the element here is a Matrix.TO * pointing to a Parsee user. */ - Concat("("); for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); @@ -162,9 +161,9 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) Concat(subxep); Free(subxep); } - Concat(" points to "); + Concat("< "); Concat(href); - Concat(" )"); + Concat(" >"); } else { diff --git a/src/Routes/Transactions.c b/src/Routes/Transactions.c index 580e43c..4ef0bee 100644 --- a/src/Routes/Transactions.c +++ b/src/Routes/Transactions.c @@ -34,6 +34,10 @@ RouteHead(RouteTxns, arr, argp) for (i = 0; i < ArraySize(events); i++) { HashMap *event = JsonValueAsObject(ArrayGet(events, i)); + if (ExtensionPushEvent(args->data->exts, event)) + { + continue; + } ParseeEventHandler(args->data, event); } diff --git a/src/XMPPCommand/Commands.c b/src/XMPPCommand/Commands.c index 7b418c6..8d00598 100644 --- a/src/XMPPCommand/Commands.c +++ b/src/XMPPCommand/Commands.c @@ -4,20 +4,10 @@ #include #include -struct XMPPCommand { - XMPPCmdCallback callback; - char *node, *name; - - char *form_instruction; - char *form_title; - - /* TODO: On-the-fly generation of options */ - Array *options; - XMPPOptionWriter otf; -}; +#include "XMPPCommand/Internal.h" XMPPCommand * -XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) +XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback callback_funct) { XMPPCommand *cmd; @@ -32,6 +22,7 @@ XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) cmd->name = StrDuplicate(name); cmd->form_instruction = NULL; cmd->form_title = NULL; + cmd->flags = flags; /* No options -> no form required */ cmd->options = NULL; @@ -127,7 +118,7 @@ XMPPFormifyCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from) ArrayFree(cmd->options); cmd->options = NULL; - cmd->otf(m, cmd, from); + cmd->otf(m, cmd, from, cmd->node); } if (!cmd->options) { @@ -184,13 +175,13 @@ XMPPCommandRequiresForm(XMPPCommand *cmd) return cmd ? (cmd->otf || !!cmd->options) : false; } void -XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form) +XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node) { - if (!m || !cmd || !from || !to) + if (!m || !cmd || !from || !to || !node) { return; } - cmd->callback(m, from, form, to); + cmd->callback(m, from, form, to, node); } bool diff --git a/src/XMPPCommand/Internal.h b/src/XMPPCommand/Internal.h new file mode 100644 index 0000000..264e83e --- /dev/null +++ b/src/XMPPCommand/Internal.h @@ -0,0 +1,17 @@ +#include + +#include + +struct XMPPCommand { + XMPPCmdCallback callback; + char *node, *name; + + char *form_instruction; + char *form_title; + + /* TODO: On-the-fly generation of options */ + Array *options; + XMPPOptionWriter otf; + + XMPPCommandFlags flags; +}; diff --git a/src/XMPPCommand/Manager.c b/src/XMPPCommand/Manager.c index 00a8c89..b272fb7 100644 --- a/src/XMPPCommand/Manager.c +++ b/src/XMPPCommand/Manager.c @@ -8,6 +8,8 @@ #include +#include "XMPPCommand/Internal.h" + typedef struct XMPPSession { char *identifier; @@ -323,7 +325,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) XMLAddAttr(command_xml, "node", node); XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "sessionid", session_id); - XMPPExecuteCommand(m, cmd, from, command_xml, NULL); + XMPPExecuteCommand(m, cmd, from, command_xml, NULL, node); XMLAddChild(iq, command_xml); XMPPSendStanza(jabber, iq, data->config->max_stanza_size); @@ -363,7 +365,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) XMLAddAttr(command_xml, "node", node); XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "sessionid", session_given); - XMPPExecuteCommand(m, cmd, from, command_xml, x_form); + XMPPExecuteCommand(m, cmd, from, command_xml, x_form, node); XMLAddChild(iq, command_xml); XMPPSendStanza(jabber, iq, data->config->max_stanza_size); @@ -385,3 +387,15 @@ XMPPGetManagerCookie(XMPPCommandManager *manager) { return manager ? manager->cookie : NULL; } +XMPPCommandFlags +XMPPGetCommandFlags(XMPPCommandManager *m, char *id) +{ + XMPPCommand *cmd; + if (!m || !id) + { + return XMPPCMD_ALL; + } + + cmd = HashMapGet(m->commands, id); + return cmd ? cmd->flags : XMPPCMD_ALL; +} diff --git a/src/XMPPCommands/AddAdmin.c b/src/XMPPCommands/AddAdmin.c index 321d18d..70f4479 100644 --- a/src/XMPPCommands/AddAdmin.c +++ b/src/XMPPCommands/AddAdmin.c @@ -12,7 +12,7 @@ #include void -AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -23,6 +23,8 @@ AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement GetFieldValue(glob, "glob", form); + (void) node; + if (!ParseeIsAdmin(data, trimmed)) { SetNote("error", "User is not authorised to execute command."); diff --git a/src/XMPPCommands/AddNofly.c b/src/XMPPCommands/AddNofly.c index 80f81e5..2b7f0ff 100644 --- a/src/XMPPCommands/AddNofly.c +++ b/src/XMPPCommands/AddNofly.c @@ -12,7 +12,7 @@ #include void -AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -21,6 +21,8 @@ AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement GetFieldValue(entity, "entity", form); GetFieldValue(reason, "reason", form); + (void) node; + if (!ParseeIsAdmin(data, trimmed)) { SetNote("error", "User is not authorised to execute command."); diff --git a/src/XMPPCommands/Admins.c b/src/XMPPCommands/Admins.c index 5749165..36c4f9b 100644 --- a/src/XMPPCommands/Admins.c +++ b/src/XMPPCommands/Admins.c @@ -12,7 +12,7 @@ #include void -AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); size_t i; @@ -20,6 +20,8 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement * XMLElement *title; XMLElement *reported, *item, *field, *value, *txt; + (void) node; + x = XMLCreateTag("x"); title = XMLCreateTag("title"); diff --git a/src/XMPPCommands/Cleanup.c b/src/XMPPCommands/Cleanup.c index 22780ff..a6fdb8b 100644 --- a/src/XMPPCommands/Cleanup.c +++ b/src/XMPPCommands/Cleanup.c @@ -12,7 +12,7 @@ #include void -CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -31,4 +31,5 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o SetNote("info", "Parsee data was sucessfully cleant up."); (void) form; + (void) node; } diff --git a/src/XMPPCommands/DelAdmin.c b/src/XMPPCommands/DelAdmin.c index 603b09f..f4ada63 100644 --- a/src/XMPPCommands/DelAdmin.c +++ b/src/XMPPCommands/DelAdmin.c @@ -31,7 +31,7 @@ DelAdmin(Array *admin_list, char *glob) return removed; } void -DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -84,9 +84,11 @@ DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement } SetNote("info", "Sucessfully removed admins"); /* TODO */ + + (void) node; } void -FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from) +FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from, char *node) { ParseeData *data = XMPPGetManagerCookie(m); DbRef *admins; @@ -120,4 +122,6 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from) DbUnlock(data->db, admins); XMPPAddOption(cmd, admin_opt); + + (void) node; } diff --git a/src/XMPPCommands/MUCInformation.c b/src/XMPPCommands/MUCInformation.c index 53dc684..c96b0ed 100644 --- a/src/XMPPCommands/MUCInformation.c +++ b/src/XMPPCommands/MUCInformation.c @@ -12,7 +12,7 @@ #include void -MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *muc = ParseeTrimJID(from); @@ -29,9 +29,10 @@ MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement Free(room_id); Free(chat_id); (void) form; + (void) node; } void -MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *muc = ParseeTrimJID(from); @@ -46,4 +47,5 @@ MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen Free(msg); Free(chat_id); (void) form; + (void) node; } diff --git a/src/XMPPCommands/MUCKV.c b/src/XMPPCommands/MUCKV.c index 6dba478..97bf7d2 100644 --- a/src/XMPPCommands/MUCKV.c +++ b/src/XMPPCommands/MUCKV.c @@ -15,7 +15,7 @@ #include void -MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *affiliation, *role; @@ -51,9 +51,10 @@ end: Free(chat_id); Free(muc); (void) form; + (void) node; } void -MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *affiliation = NULL, *role = NULL; @@ -114,4 +115,5 @@ end: Free(chat_id); Free(muc); (void) form; + (void) node; } diff --git a/src/XMPPCommands/MUCUnlink.c b/src/XMPPCommands/MUCUnlink.c index f5560d3..a553824 100644 --- a/src/XMPPCommands/MUCUnlink.c +++ b/src/XMPPCommands/MUCUnlink.c @@ -14,7 +14,7 @@ #include void -MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *affiliation, *role; @@ -64,4 +64,5 @@ MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) Free(room); Free(muc); (void) form; + (void) node; } diff --git a/src/XMPPCommands/Nofly.c b/src/XMPPCommands/Nofly.c index dcbef67..05053b4 100644 --- a/src/XMPPCommands/Nofly.c +++ b/src/XMPPCommands/Nofly.c @@ -14,7 +14,7 @@ #define TITLE "Parsee global bans" void -NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -89,4 +89,5 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o } (void) form; + (void) node; } diff --git a/src/XMPPCommands/Status.c b/src/XMPPCommands/Status.c index 1b19129..e954eef 100644 --- a/src/XMPPCommands/Status.c +++ b/src/XMPPCommands/Status.c @@ -12,7 +12,7 @@ #include void -StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { char *trimmed = ParseeTrimJID(from); size_t alloc = MemoryAllocated(); @@ -69,5 +69,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement * XMLAddChild(out, x); (void) form; + (void) node; (void) m; } diff --git a/src/XMPPCommands/Whitelist.c b/src/XMPPCommands/Whitelist.c index 8e097a8..e9b4d01 100644 --- a/src/XMPPCommands/Whitelist.c +++ b/src/XMPPCommands/Whitelist.c @@ -12,7 +12,7 @@ #include void -ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -35,9 +35,10 @@ ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLE SetNote("info", "Parsee whitelist was removed."); (void) form; + (void) node; } void -AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -79,9 +80,10 @@ AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLEle DbUnlock(data->db, ref); SetNote("info", "Server successfully whitelisted."); + (void) node; } void -WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -139,4 +141,5 @@ WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen } (void) form; + (void) node; } diff --git a/src/XMPPThread/PEPs/VCard.c b/src/XMPPThread/PEPs/VCard.c index 086e248..896d518 100644 --- a/src/XMPPThread/PEPs/VCard.c +++ b/src/XMPPThread/PEPs/VCard.c @@ -94,15 +94,13 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item) AddTextField(vcard, "fn", name); AddTextField(vcard, "nickname", mxid); - AddURIField(vcard, "url", REPOSITORY); AddURIField(vcard, "url", "https://kappach.at/parsee"); - AddURIField(vcard, "url", "https://matrix.org"); AddURIField(vcard, "url", m_to); AddTextField( vcard, "note", - "This is a bridged Matrix user, from Parsee." + "This is a bridged Matrix user, from " NAME "." ); Free(mxid); Free(name); diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 559ffcb..3c86feb 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -65,6 +65,16 @@ XMPPDispatcher(void *argp) Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); } + if (ExtensionPushStanza(args->exts, stanza, STANZA_RAW)) + { + if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Stanza was vetoed by an extension."); + } + XMLFreeElement(stanza); + continue; + } + if (StrEquals(stanza->name, "presence")) { PresenceStanza(args, stanza, thread); @@ -130,6 +140,7 @@ bool XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza) { ParseeData *args = XMPPGetManagerCookie(m); + XMPPCommandFlags flags; char *trimmed_from; char *from; char *chat_id; @@ -144,28 +155,24 @@ XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza) is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from)); Free(trimmed_from); Free(chat_id); -#define XMPP_COMMAND(f,l,n,t,s) \ - if (StrEquals(n, id)) \ - { \ - if (l == XMPPCMD_ALL) \ - { \ - return true; \ - } \ - else if (l == XMPPCMD_MUC) \ - { \ - return is_muc; \ - } \ - else if (l == XMPPCMD_ADMINS) \ - { \ - bool is_admin; \ - trimmed_from = ParseeTrimJID(from); \ - is_admin = ParseeIsAdmin(args, trimmed_from); \ - Free(trimmed_from); \ - return is_admin; \ - } \ + flags = XMPPGetCommandFlags(m, id); + + if (flags == XMPPCMD_ALL) + { + return true; + } + else if (flags == XMPPCMD_MUC) + { + return is_muc; + } + else if (flags == XMPPCMD_ADMINS) + { + bool is_admin; + trimmed_from = ParseeTrimJID(from); + is_admin = ParseeIsAdmin(args, trimmed_from); + Free(trimmed_from); + return is_admin; } - XMPPCOMMANDS -#undef XMPP_COMMAND return false; } @@ -191,13 +198,14 @@ ParseeXMPPThread(void *argp) XMPPCommand *cmd; #define XMPP_COMMAND(f,l,n,t,s) \ cmd = XMPPBasicCmd( \ - n, t, f \ + l, n, t, f \ ); \ s \ XMPPRegisterCommand(info.m, cmd); XMPPCOMMANDS #undef XMPP_COMMAND } + ExtensionRequestCommands(args->exts); info.pep_manager = CreatePEPManager(args, &info); { PEPManagerAddEvent( diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 32aa0c9..947c169 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -422,10 +422,16 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); + XMPPShoveCommandList(thr->info->m, IsInMUC(args, from) ? parsee_muc_jid : to, q, stanza ); + ExtensionShoveCommands( + args->exts, + IsInMUC(args, from) ? parsee_muc_jid : to, + q, stanza + ); XMLAddChild(iq_reply, q); Free(parsee_muc_jid); } @@ -612,6 +618,7 @@ IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPCommandManager *manager = thr->info->m; XMPPManageCommand(manager, stanza, args); + ExtensionManageCommands(args->exts, stanza); } #undef DISCO diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 3c57e69..220cca4 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -411,7 +411,17 @@ end_error: /* Check if it is a media link */ oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); - if (oob && data && media_enabled) + + /* TODO: This may be way too late. I think a way to listen to *all* + * stanzas may be worthwhile. */ + if (ExtensionPushStanza(args->exts, stanza, STANZA_MESSAGE)) + { + //if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Stanza was vetoed by an extension."); + } + } + else if (oob && data && media_enabled) { char *mxc, *mime = NULL; HashMap *content = NULL; diff --git a/src/XMPPThread/Stanzas/Presence.c b/src/XMPPThread/Stanzas/Presence.c index fb6011b..65c2082 100644 --- a/src/XMPPThread/Stanzas/Presence.c +++ b/src/XMPPThread/Stanzas/Presence.c @@ -163,6 +163,8 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) int power_level = 0; char *parsee = ParseeMXID(args); char *parsee_j = ParseeJID(args); + char *muc = ParseeTrimJID(HashMapGet(stanza->attrs, "from")); + char *parsee_muc = StrConcat(3, muc, "/", "parsee"); Free(trim); if (!item) @@ -206,15 +208,12 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) if (!StrEquals(HashMapGet(stanza->attrs, "to"), parsee_j) && IsStatus(110)) { - char *muc = ParseeTrimJID(HashMapGet(stanza->attrs, "from")); char *usr = HashMapGet(stanza->attrs, "to"); /* Ask for voice in a visitor self-presence. We do not notify * the user, as an error MUST occur, which is handled and * logged out. */ XMPPRequestVoice(args->jabber, usr, muc); - - Free(muc); } } @@ -270,7 +269,8 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } } if (StrEquals(type, "unavailable") && - StrEquals(dst, parsee_j) && IsStatus(301)) + (StrEquals(jid, parsee_muc) || StrEquals(jid, parsee_j)) + && IsStatus(301)) { char *chat_id = ParseeGetFromRoomID(args, room); @@ -287,7 +287,9 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } end_item: + Free(muc); Free(from); + Free(parsee_muc); Free(decode_from); Free(real_matrix); Free(matrix_user_pl); diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 3a158b5..5931f3d 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -68,7 +68,6 @@ int IdentitySort(void *idap, void *idbp); char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza); char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id); char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza); -void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event); bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza); HashMap * ShoveStanza(HashMap *content, XMLElement *stanza); @@ -82,9 +81,6 @@ void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr); bool ServerHasXEP421(ParseeData *data, char *from); -char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force); -#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL) - PEPManager * CreatePEPManager(ParseeData *data, void *cookie); void * PEPManagerCookie(PEPManager *manager); void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event); diff --git a/src/include/Extension.h b/src/include/Extension.h new file mode 100644 index 0000000..54123d5 --- /dev/null +++ b/src/include/Extension.h @@ -0,0 +1,81 @@ +#ifndef PARSEE_EXTENSION_H +#define PARSEE_EXTENSION_H + +#include + +#include + +#include + +typedef struct ParseeData ParseeData; + +typedef struct Extensions Extensions; +typedef struct Extension Extension; + +typedef enum StanzaType { + STANZA_RAW = 0, + STANZA_MESSAGE = 1, + STANZA_IQ = 2, + STANZA_PRESENCE = 3, + + STANZA_TYPE_COUNT +} StanzaType; + +/** Verifies if extensions are enabled with the Parsee binary. + * Functions here WILL fail if this function + * returns false! + * ------------------- + * Returns: true IFF extensions are enabled */ +extern bool ExtensionEnabled(void); + +/* Creates an extension context. */ +extern Extensions * ExtensionCreateContext(struct ParseeData *data); + +/** Destroys the entire extension context, and all of its extensions. + * ------------------------------------- + * Returns: NOTHING + * Thrashes: ctx */ +extern void ExtensionDestroyContext(Extensions *ctx); + +/** Loads a basic extension from a simple Janet file. + * ---------------------------- + * Returns: An extension handle[ctx] | NULL */ +extern Extension * ExtensionLoadBasic(Extensions *ctx, char *id, char *file); + +/** Reloads an extension from an ID. + * --------------------------- + * Returns: NOTHING */ +extern void ExtensionReload(Extensions *ctx, char *id); + +/** Loads extensions from a directory. The directory's Janet files are loaded + * (with the part before the .janet extension denoting the identifier). + * ----------------------------------- + * Returns: NOTHING */ +extern void ExtensionLoadDir(Extensions *ctx, char *dir); + +/** Broadcasts a stanza received to all extensions(by calling the + * on-stanza function). + * ----------------------------------------- + * Returns: NOTHING + * Modifies: the extensions */ +extern bool ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t); + +/** Broadcasts a Matrix event to all extensions, akin to ExtensionPushStanza. + * ----------- + * Returns: NOTHING + * Modifies: the extensions */ +extern bool ExtensionPushEvent(Extensions *ctx, HashMap *obj); + +/** Calls "on-xmpp-cmd" on extensions' Janet side, with no arguments. + * ---------- + * Returns: NOTHING + * Modifies: the extensions */ +extern void ExtensionRequestCommands(Extensions *ctx); + +extern void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza); + +extern void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza); + +#endif diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 30c8b61..5993877 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -15,6 +15,7 @@ typedef struct ParseeData ParseeData; #include +#include #include #include @@ -50,6 +51,7 @@ typedef struct ParseeConfig { /* ------- DB -------- */ char *db_path; size_t db_size; + char *extensions; /* - COMMAND-LINE FLAGS - */ int xmpp_threads, http_threads; @@ -72,8 +74,10 @@ typedef struct ParseeData { pthread_mutex_t oidl; /* If Parsee was intentionally halted */ - bool halted; + volatile bool halted; pthread_mutex_t halt_lock; + + Extensions *exts; } ParseeData; typedef struct Argument { @@ -93,6 +97,8 @@ typedef struct Argument { #define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__)) #define GrabArray(obj, ...) JsonValueAsArray(JsonGet(obj, __VA_ARGS__)) +#define IterateReentrant(h, k, v, i) HashMapIterateReentrant(h, &k, (void **) &v, &i) + /* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTES * in timestamps */ #define SECONDS * 1000 @@ -121,6 +127,7 @@ extern const char *parsee_ascii[PARSEE_ASCII_LINES]; * Modifies: the logger output */ extern void ParseePrintASCII(void); + /** * Checks if two versions of Parsee can be considered "compatible". * This is mainly used for things like database operations. TODO: @@ -276,6 +283,12 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id); /* Finds the MUC JID from a chat ID */ extern char * ParseeGetMUCID(ParseeData *, char *chat_id); +/** Verifies if a JID maps to a chatroom through Service Discovery. + * ---------------- + * Returns: whenever the JID is a real MUC + * Modifies: the XMPP stream */ +extern bool ParseeIsMUC(ParseeData *data, char *jid); + /** Fetches a configuration value from a key in a chat(given a Chat ID), * as a string or NULL. Keys are to be stored like Java packages(reveres DNS). * Parsee has the right over any key with the 'p.' prefix. @@ -321,6 +334,14 @@ ParseeIsMediaEnabled(ParseeData *data, char *chat_id); extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender); +/** Automatically pushes the link between a stanza and a bridged Matrix event. + * It behaves like {ParseePushStanza} and {ParseePushDMStanza}, but the routing + * is done automatically. + * ---------------------------- + * Returns: NOTHING + * Modifies: the database */ +extern void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event); + /* Checks if a stanza is not duplicated in a chat ID */ extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id); @@ -488,4 +509,8 @@ extern void ParseeUnlinkRoom(ParseeData *data, char *chat_id); * Modifies: NOTHING */ extern bool ParseeIsMUCWhitelisted(ParseeData *data, char *muc); +/* TODO */ +extern char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force); +#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL) + #endif diff --git a/src/include/XMPPCommand.h b/src/include/XMPPCommand.h index 33b121f..8fa0a03 100644 --- a/src/include/XMPPCommand.h +++ b/src/include/XMPPCommand.h @@ -1,3 +1,4 @@ +typedef struct XMPPCommandManager XMPPCommandManager; #ifndef PARSEE_XMPPCOMMAND_H #define PARSEE_XMPPCOMMAND_H @@ -7,11 +8,16 @@ #include #include -typedef struct XMPPCommandManager XMPPCommandManager; +typedef enum XMPPCommandFlags { + XMPPCMD_ALL = 0, + XMPPCMD_MUC , /* Only for MUCs */ + XMPPCMD_ADMINS /* Only for administrators */ +} XMPPCommandFlags; + typedef struct XMPPCommand XMPPCommand; typedef struct XMPPOption XMPPOption; -typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *); -typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *); +typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); +typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *, char *); typedef bool (*XMPPCmdFilter)(XMPPCommandManager *, char *id, XMLElement *stanza); /** Creates a simple XMPP command manager, which routes commands @@ -39,7 +45,7 @@ XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter); * Modifies: NOTHING * See-Also: XMPPRegisterCommand */ extern XMPPCommand * - XMPPBasicCmd(char *node, char *name, XMPPCmdCallback cb); +XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback cb); extern void XMPPCmdOptionsCreator(XMPPCommand *cmd, XMPPOptionWriter writer); extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt); @@ -49,7 +55,7 @@ extern char * XMPPGetCommandDesc(XMPPCommand *cmd); extern void XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction); extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title); extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); -extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form); +extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node); /** Create a basic option. * ----------------------------------------------------- @@ -91,6 +97,12 @@ extern void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd); * See-Also: XMPPCreateManager */ extern void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s); +/** Returns the flags associated to a command. + * ---------------- + * Returns: some flags + * Modifies: NOTHING */ +extern XMPPCommandFlags XMPPGetCommandFlags(XMPPCommandManager *m, char *id); + /** Destroys all memory related to the command {manager}. * ----------------------------------------------------- * Returns: NOTHING @@ -111,11 +123,11 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD #define XMPP_COMMAND(f,l,n,t,s) \ extern void \ - f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); \ + f(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); \ /* This symbol might not exist. We do, however, not care, as * it is not always done. */ \ extern void \ - Form##f(XMPPCommandManager *, XMPPCommand *, char *); \ + Form##f(XMPPCommandManager *, XMPPCommand *, char *, char *); \ #include "XMPPCommands.x.h" diff --git a/src/include/XMPPCommands.x.h b/src/include/XMPPCommands.x.h index dba6f48..483aca9 100644 --- a/src/include/XMPPCommands.x.h +++ b/src/include/XMPPCommands.x.h @@ -1,9 +1,4 @@ /* C X-macro file */ -typedef enum XMPPCommandFlags { - XMPPCMD_ALL = 0, - XMPPCMD_MUC , /* Only for MUCs */ - XMPPCMD_ADMINS /* Only for administrators */ -} XMPPCommandFlags; #define XMPPCOMMANDS \ XMPP_COMMAND(StatusCallback, XMPPCMD_ALL, "stats", "Get Parsee statistics", {}) \ XMPP_COMMAND(CleanCallback, XMPPCMD_ADMINS, "clean", "Cleanup temporary Parsee data", {}) \ From 2e566c73fcca4b9ba940eb37c9bddea3c6d1f519 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 29 Nov 2024 13:58:49 +0100 Subject: [PATCH 02/45] [MOD] Start not hardcoding discovery features The eventual goal here is to have different disco replies for specific kinds of JID, effectively allowing us to have MUCs. Yeah. --- src/XMPP/MUC.c | 25 +++++++- src/XMPP/Stanza.c | 21 ------ src/XMPPThread/Caps.c | 125 +++++++++++++++++++++++++++--------- src/XMPPThread/Stanzas/IQ.c | 10 ++- src/XMPPThread/internal.h | 25 +++++++- src/include/XMPP.h | 10 --- 6 files changed, 149 insertions(+), 67 deletions(-) diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 7064698..5813cd2 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -8,6 +8,8 @@ #include #include +#include "XMPPThread/internal.h" + bool XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) { @@ -162,6 +164,7 @@ bool XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret) { XMLElement *presence, *x, *reply, *history, *photo; + IQFeatures *features; char *from, *id, *stime = "3600"; if (!comp || !fr || !muc) { @@ -189,7 +192,15 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool XMLAddChild(x, history); XMLAddChild(presence, x); - XMPPAnnotatePresence(presence); + features = CreateIQFeatures(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + XMPPAnnotatePresence(presence, features); + FreeIQFeatures(features); if (hash) { @@ -225,6 +236,7 @@ void XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) { XMLElement *presence; + IQFeatures *features; char *from, *id; if (!comp || !fr || !muc) { @@ -246,7 +258,16 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMLAddChild(presence, status); } - XMPPAnnotatePresence(presence); + features = CreateIQFeatures(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + XMPPAnnotatePresence(presence, features); + FreeIQFeatures(features); + XMPPSendStanza(comp, presence, 10000); diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 451c16f..27a658e 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -159,27 +159,6 @@ XMPPGetReply(XMLElement *elem) return HashMapGet(rep->attrs, "id"); } -void -XMPPAnnotatePresence(XMLElement *presence) -{ - XMLElement *c; - char *ver; - if (!presence) - { - return; - } - - ver = XMPPGenerateVer(); - c = XMLCreateTag("c"); - XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps"); - XMLAddAttr(c, "hash", "sha-1"); - XMLAddAttr(c, "node", REPOSITORY); - XMLAddAttr(c, "ver", ver); - - Free(ver); - XMLAddChild(presence, c); -} - char * XMPPGetModeration(XMLElement *stanza) { diff --git a/src/XMPPThread/Caps.c b/src/XMPPThread/Caps.c index 867adbd..5e53ee1 100644 --- a/src/XMPPThread/Caps.c +++ b/src/XMPPThread/Caps.c @@ -12,33 +12,86 @@ #include "XMPPThread/internal.h" +IQFeatures * +CreateIQFeatures(void) +{ + IQFeatures *ret = Malloc(sizeof(*ret)); + + ret->identity = ArrayCreate(); + ret->adverts = ArrayCreate(); + + return ret; +} +void +FreeIQFeatures(IQFeatures *features) +{ + size_t i; + if (!features) + { + return; + } + + for (i = 0; i < ArraySize(features->adverts); i++) + { + Free(ArrayGet(features->adverts, i)); + } + ArrayFree(features->adverts); + + for (i = 0; i < ArraySize(features->identity); i++) + { + XMPPIdentity *identity = ArrayGet(features->identity, i); + + Free(identity->category); + Free(identity->type); + Free(identity->lang); + Free(identity->name); + + Free(identity); + } + ArrayFree(features->identity); + + Free(features); +} + +void +AdvertiseIQFeature(IQFeatures *f, char *feature) +{ + if (!f || !feature) + { + return; + } + + ArrayAdd(f->adverts, StrDuplicate(feature)); +} +void +AddIQIdentity(IQFeatures *f, char *cat, char *lang, char *type, char *name) +{ + XMPPIdentity *identity; + if (!f) + { + return; + } + + identity = Malloc(sizeof(*identity)); + identity->category = StrDuplicate(cat); + identity->type = StrDuplicate(type); + identity->lang = StrDuplicate(lang); + identity->name = StrDuplicate(name); + ArrayAdd(f->identity, identity); +} /* Generates a SHA-256 hash of the ver field. */ char * -XMPPGenerateVer(void) +XMPPGenerateVer(IQFeatures *features) { char *S = NULL; unsigned char *Sha = NULL; - Array *identities = ArrayCreate(); - Array *features = ArrayCreate(); size_t i; /* Initialise identity table, to be sorted */ -#define IdentitySimple(cat, Type, Name) { \ - XMPPIdentity *id = Malloc(sizeof(*id)); \ - id->category = cat; \ - id->lang = NULL; \ - id->type = Type; \ - id->name = Name; \ - ArrayAdd(identities, id); } - IQ_IDENTITY -#undef IdentitySimple -#define AdvertiseSimple(feature) ArrayAdd(features, feature); - IQ_ADVERT -#undef AdvertiseSimple - ArraySort(identities, IdentitySort); - for (i = 0; i < ArraySize(identities); i++) + ArraySort(features->identity, IdentitySort); + for (i = 0; i < ArraySize(features->identity); i++) { - XMPPIdentity *identity = ArrayGet(identities, i); + XMPPIdentity *identity = ArrayGet(features->identity, i); char *id_chunk = StrConcat(7, identity->category, "/", identity->type, "/", @@ -50,10 +103,10 @@ XMPPGenerateVer(void) Free(id_chunk); } - ArraySort(features, ((int (*) (void *, void *)) ICollate)); - for (i = 0; i < ArraySize(features); i++) + ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate)); + for (i = 0; i < ArraySize(features->adverts); i++) { - char *feature = ArrayGet(features, i); + char *feature = ArrayGet(features->adverts, i); char *tmp = S; S = StrConcat(3, S, feature, "<"); Free(tmp); @@ -64,16 +117,28 @@ XMPPGenerateVer(void) S = Base64Encode((const char *) Sha, 20); Free(Sha); - ArrayFree(features); - for (i = 0; i < ArraySize(identities); i++) - { - XMPPIdentity *identity = ArrayGet(identities, i); - /* We don't have to do anything here. */ - Free(identity); - } - ArrayFree(identities); - return S; } +void +XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features) +{ + XMLElement *c; + char *ver; + if (!presence || !features) + { + return; + } + + ver = XMPPGenerateVer(features); + c = XMLCreateTag("c"); + XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps"); + XMLAddAttr(c, "hash", "sha-1"); + XMLAddAttr(c, "node", REPOSITORY); + XMLAddAttr(c, "ver", ver); + + Free(ver); + XMLAddChild(presence, c); +} + diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 4ced5dc..396fec4 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -151,6 +151,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; + IQFeatures *features = CreateIQFeatures(); from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); @@ -164,8 +165,14 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) XMLAddAttr(iq_reply, "id", id); query = IQGenerateQuery(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple { - char *ver = XMPPGenerateVer(); + char *ver = XMPPGenerateVer(features); char *node = StrConcat(3, REPOSITORY, "#", ver); XMLAddAttr(query, "node", node); @@ -173,6 +180,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) Free(ver); } XMLAddChild(iq_reply, query); + FreeIQFeatures(features); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMLFreeElement(iq_reply); diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 3a158b5..8602621 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -32,13 +32,17 @@ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("component", "generic", "Parsee's component") -typedef struct PEPManager PEPManager; -typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); - +typedef struct IQFeatures { + Array *identity; + Array *adverts; +} IQFeatures; typedef struct XMPPIdentity { char *category, *type, *lang, *name; } XMPPIdentity; +typedef struct PEPManager PEPManager; +typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); + typedef struct XMPPThread XMPPThread; typedef struct XMPPThreadInfo { /* A FIFO of stanzas inbound, to be read by dispatcher @@ -65,6 +69,21 @@ struct XMPPThread { int ICollate(unsigned char *cata, unsigned char *catb); int IdentitySort(void *idap, void *idbp); +IQFeatures * CreateIQFeatures(void); +void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *name); +void AdvertiseIQFeature(IQFeatures *, char *feature); +void FreeIQFeatures(IQFeatures *); + +/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. + * -------- + * Returns: A base64 encoded ver hash[LA:HEAP] + * Modifies: NOTHING + * See-Also: https://xmpp.org/extensions/xep-0115.html */ +char * XMPPGenerateVer(IQFeatures *features); + +/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */ +void XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features); + char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza); char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id); char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza); diff --git a/src/include/XMPP.h b/src/include/XMPP.h index b2c5293..92a5d80 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -98,16 +98,6 @@ extern char * XMPPGetReply(XMLElement *elem); * See-Also: https://xmpp.org/extensions/xep-0425.html */ extern char * XMPPGetModeration(XMLElement *stanza); -/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. - * -------- - * Returns: A base64 encoded ver hash[LA:HEAP] - * Modifies: NOTHING - * See-Also: https://xmpp.org/extensions/xep-0115.html */ -extern char * XMPPGenerateVer(void); - -/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */ -extern void XMPPAnnotatePresence(XMLElement *presence); - extern bool XMPPHasError(XMLElement *stanza, char *type); #include From 5ddc5d3e5cb4aec90ba260f7114fa18c4b902162 Mon Sep 17 00:00:00 2001 From: LDA Date: Thu, 19 Dec 2024 20:59:12 +0100 Subject: [PATCH 03/45] [WIP/ADD] Try to separate discovery, make links nicer --- configure.c | 71 +++------------- src/Main.c | 3 +- src/MatrixID.c | 34 ++++++++ src/Parsee/Data.c | 2 + src/Parsee/Utils/Formatting.c | 48 ++++++++--- src/Routes/Media.c | 43 +++++++--- src/XMPP/MUC.c | 4 +- src/XMPP/MUCServ.c | 153 ++++++++++++++++++++++++++++++++++ src/XMPPThread/Caps.c | 37 ++++++++ src/XMPPThread/ReListener.c | 6 ++ src/XMPPThread/Stanzas/IQ.c | 69 +++++++-------- src/XMPPThread/internal.h | 8 ++ src/include/MUCServ.h | 39 +++++++++ src/include/Matrix.h | 7 ++ src/include/Parsee.h | 3 + 15 files changed, 409 insertions(+), 118 deletions(-) create mode 100644 src/XMPP/MUCServ.c create mode 100644 src/include/MUCServ.h diff --git a/configure.c b/configure.c index a72a68a..d3a01d0 100644 --- a/configure.c +++ b/configure.c @@ -90,22 +90,6 @@ string_cat(char *in1, char *in2) return out; } -static char * -trim_nl(char *in) -{ - char *tc; - if (!in) - { - return NULL; - } - - while ((tc = strrchr(in, '\n'))) - { - *tc = '\0'; - } - - return in; -} typedef struct str_array { char **values; @@ -115,6 +99,10 @@ static str_array_t * str_array_create(void) { str_array_t *ret = malloc(sizeof(*ret)); + if (!ret) + { + return NULL; + } ret->values = NULL; ret->quantity = 0; @@ -216,36 +204,6 @@ failure: free(line); return NULL; } -static int -exec_code(char *program, char *argv[]) -{ - pid_t forkRet; - if (!program || !argv) - { - return -1; - } - - forkRet = fork(); - if (forkRet == 0) - { - /* Child */ - execvp(program, argv); - exit(0); - } - else - { - /* Parent */ - int status; - if (waitpid(forkRet, &status, 0) == -1) - { - return -1; - } - return status; - } - - /* We're not meant to ever be there, but TCC is stupid. */ - return -1; -} static char * strchrn(char *s, char c) @@ -253,17 +211,6 @@ strchrn(char *s, char c) s = strchr(s, c); return s ? s+1 : NULL; } -static char * -strchrl(char *s, char c) -{ - char *s1 = NULL; - while ((s = strchr(s, c))) - { - s1 = s; - s++; - } - return s1; -} static str_array_t * split(char *dir) { @@ -512,8 +459,7 @@ main_build(int argc, char *argv[]) { FILE *makefile; char *repo = cmd_stdout("git remote get-url origin"); - size_t size, i; - ssize_t nread; + size_t i; bool with_static = false, with_lmdb = false; int opt; str_array_t *sources, *images, *utils, *aya; @@ -546,6 +492,13 @@ main_build(int argc, char *argv[]) } makefile = fopen("Makefile", "w"); + if (!makefile) + { + fprintf(stderr, "Couldn't create Makefile.\n"); + fprintf(stderr, "This isn't good, actually.\n"); + + return EXIT_FAILURE; + } fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n"); fprintf(makefile, "# Ideally do not touch, unless you have a very "); fprintf(makefile, "good reason to do it. \n\n"); diff --git a/src/Main.c b/src/Main.c index a9b0a50..633e117 100644 --- a/src/Main.c +++ b/src/Main.c @@ -72,12 +72,13 @@ ParseeCheckMatrix(void *datp) Log(LOG_ERR, "Please check if your homeserver is active."); Log(LOG_ERR, "%s shall now exit...", NAME); + /* TODO: SEGV! */ pthread_mutex_lock(&data->halt_lock); data->halted = true; pthread_mutex_unlock(&data->halt_lock); XMPPFinishCompStream(data->jabber); - pthread_join(xmpp_thr, NULL); + //pthread_join(xmpp_thr, NULL); Log(LOG_INFO, "Stopping server..."); HttpServerStop(data->server); } diff --git a/src/MatrixID.c b/src/MatrixID.c index 4488fd4..bb7c584 100644 --- a/src/MatrixID.c +++ b/src/MatrixID.c @@ -1,6 +1,9 @@ #include #include +#include +#include +#include #include @@ -32,3 +35,34 @@ MatrixParseID(char *user) return ret; } +UserID * +MatrixParseIDFromMTO(Uri *uri) +{ + UserID *id = NULL; + char *path, *params, *decoded; + if (!uri) + { + return NULL; + } + + if (!StrEquals(uri->proto, "https") || !StrEquals(uri->host, "matrix.to")) + { + return NULL; + } + if (strncmp(uri->path, "/#/", 3)) + { + return NULL; + } + path = StrDuplicate(uri->path + 3); + params = path ? strchr(path, '?') : NULL; + if (params) + { + *params = '\0'; + } + decoded = HttpUrlDecode(path); + id = MatrixParseID(decoded); + Free(decoded); + Free(path); + + return id; +} diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index cb1b64b..68ddf3c 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -27,6 +27,7 @@ ParseeInitData(XMPPComponent *comp) data->config = ParseeConfigGet(); data->router = HttpRouterCreate(); data->jabber = comp; + data->muc = CreateMUCServer(data); data->handler = CommandCreateRouter(); data->oid_servers = HashMapCreate(); @@ -116,6 +117,7 @@ ParseeFreeData(ParseeData *data) pthread_mutex_destroy(&data->halt_lock); Free(data->id); XMPPEndCompStream(data->jabber); + FreeMUCServer(data->muc); DbClose(data->db); HttpRouterFree(data->router); CommandFreeRouter(data->handler); diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index da6194f..5a2c6b8 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -151,20 +153,42 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) else if (StrEquals(elem->name, "a")) { char *href = HashMapGet(elem->attrs, "href"); - /* TODO: Check if the element here is a Matrix.TO - * pointing to a Parsee user. */ - Concat("("); - for (i = 0; i < ArraySize(elem->children); i++) + Uri *pref = UriParse(href); + if (StrEquals(pref->host, "matrix.to")) { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); + /* TODO: Check if the element here is a Matrix.TO + * pointing to a Parsee user. */ + UserID *id = MatrixParseIDFromMTO(pref); + if (id) + { + /* TODO: Detect if it already is a Parsee user */ + Concat("@"); + Concat(id->localpart); + Concat(":"); + Concat(id->server); + } + else + { + Concat(href); + } + + Free(id); } - Concat(" points to "); - Concat(href); - Concat(" )"); + else + { + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(event, child, flags); + + Concat(subxep); + Free(subxep); + } + Concat(" < "); + Concat(href); + Concat(" >"); + } + UriFree(pref); } else { diff --git a/src/Routes/Media.c b/src/Routes/Media.c index 7d6bc68..004a854 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -10,6 +10,37 @@ #include +static HttpClientContext * +TryDownload(ParseeData *data, char *server, char *identi) +{ + HttpClientContext *cctx; + char *path; + server = HttpUrlEncode(server); + identi = HttpUrlEncode(identi); + + path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi); + cctx = ParseeCreateRequest(data->config, HTTP_GET, path); + ASAuthenticateRequest(data->config, cctx); + Free(path); + + HttpRequestSendHeaders(cctx); + if (HttpRequestSend(cctx) != HTTP_OK) + { + Log(LOG_WARNING, "Failing back."); + HttpClientContextFree(cctx); + path = StrConcat(4, "/_matrix/client/v1/media/download/", server, "/", identi); + cctx = ParseeCreateRequest(data->config, HTTP_GET, path); + ASAuthenticateRequest(data->config, cctx); + Free(path); + HttpRequestSendHeaders(cctx); + HttpRequestSend(cctx); + } + + Free(server); + Free(identi); + return cctx; +} + RouteHead(RouteMedia, arr, argp) { ParseeHttpArg *args = argp; @@ -44,15 +75,7 @@ RouteHead(RouteMedia, arr, argp) /* Proxy the media through an authenticated endpoint if the HMAC * is valid. */ - server = HttpUrlEncode(server); - identi = HttpUrlEncode(identi); - path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi); - cctx = ParseeCreateRequest(args->data->config, HTTP_GET, path); - ASAuthenticateRequest(args->data->config, cctx); - Free(path); - - HttpRequestSendHeaders(cctx); - HttpRequestSend(cctx); + cctx = TryDownload(args->data, server, identi); reqh = HttpResponseHeaders(cctx); while (HashMapIterate(reqh, &key, (void **) &val)) { @@ -65,8 +88,6 @@ RouteHead(RouteMedia, arr, argp) } HttpClientContextFree(cctx); - Free(server); - Free(identi); return NULL; } diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 5813cd2..67bc9d1 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -192,7 +192,7 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool XMLAddChild(x, history); XMLAddChild(presence, x); - features = CreateIQFeatures(); + features = LookupJIDFeatures(from); #define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); IQ_IDENTITY #undef IdentitySimple @@ -258,7 +258,7 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMLAddChild(presence, status); } - features = CreateIQFeatures(); + features = LookupJIDFeatures(from); #define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); IQ_IDENTITY #undef IdentitySimple diff --git a/src/XMPP/MUCServ.c b/src/XMPP/MUCServ.c new file mode 100644 index 0000000..66f4840 --- /dev/null +++ b/src/XMPP/MUCServ.c @@ -0,0 +1,153 @@ +#include + +#include +#include +#include + +#include +#include + +#define MUCNS "http://jabber.org/protocol/muc" + +struct MUCServer { + pthread_mutex_t mut; + + ParseeData *data; +}; + +static char * +GetMUCName(char *jid) +{ + char *name, *at; + size_t len; + if (!jid || *jid != '#') + { + return NULL; + } + + jid++; + + at = strchr(jid, '@'); + if (!at) + { + return StrDuplicate(jid); + } + len = at - jid; + name = Malloc(len + 1); + memset(name, '\0', len + 1); + memcpy(name, jid, len); + + return name; +} + +MUCServer * +CreateMUCServer(ParseeData *data) +{ + MUCServer *server; + if (!data) + { + return NULL; + } + + server = Malloc(sizeof(*server)); + pthread_mutex_init(&server->mut, NULL); + server->data = data; + return server; +} + +static bool +MUCManagePresence(MUCServer *serv, XMLElement *stanza, char *from, char *to) +{ + char *name; + XMLElement *x = XMLookForTKV(stanza, "x", "xmlns", MUCNS); + char *type = HashMapGet(stanza->attrs, "type"); + if (x && !type) + { + /* The user is trying to join the MUC */ + name = GetMUCName(to); + Log(LOG_WARNING, "%s is trying to join MUC '%s'", from, name); + + /* TODO: Check if the user should be joining. If so, make them. + * Implementing MUCs is gonna be fun. */ + + /* TODO: Presence broadcast hell. */ + Free(name); + } + return false; +} + +static bool +MUCManageMessage(MUCServer *serv, XMLElement *stanza, char *from, char *to) +{ + Log(LOG_WARNING, "MUCSERV: got a message %s->%s", from, to); + return false; +} + +bool +ManageMUCStanza(MUCServer *serv, XMLElement *stanza) +{ + char *stype, *from, *to; + bool ret; + if (!serv || !stanza) + { + return false; + } + + from = HashMapGet(stanza->attrs, "from"); + to = HashMapGet(stanza->attrs, "to"); + stype = stanza->name; + + if (to && *to != '#') + { + /* We aren't interacting with a MUC at all. Don't do anything. */ + return false; + } + if (StrEquals(stype, "iq")) + { + /* TODO: Worry about IQ later */ + return false; + } + + ret = false; + pthread_mutex_lock(&serv->mut); + if (StrEquals(stype, "presence")) + { + ret = MUCManagePresence(serv, stanza, from, to); + } + else if (StrEquals(stype, "message")) + { + ret = MUCManageMessage(serv, stanza, from, to); + } + /* TODO: Do stuff while locked */ + pthread_mutex_unlock(&serv->mut); + + /* TODO: Verify the destination, and make sure we aren't doing + * anything stupid(especially with discovery) */ + return false; +} + +bool +MUCServerExists(MUCServer *serv, char *muc) +{ + if (!serv || !muc) + { + return false; + } + + return false; +} + +void +FreeMUCServer(MUCServer *serv) +{ + if (!serv) + { + return; + } + + pthread_mutex_lock(&serv->mut); + /* TODO */ + pthread_mutex_unlock(&serv->mut); + pthread_mutex_destroy(&serv->mut); + Free(serv); +} diff --git a/src/XMPPThread/Caps.c b/src/XMPPThread/Caps.c index 5e53ee1..d79a30c 100644 --- a/src/XMPPThread/Caps.c +++ b/src/XMPPThread/Caps.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -142,3 +143,39 @@ XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features) XMLAddChild(presence, c); } + +IQFeatures * +LookupJIDFeatures(char *jid) +{ + IQFeatures *features; + if (!jid) + { + return NULL; + } + + features = CreateIQFeatures(); + + if (*jid == '#') + { + /* This is a MUC. As such, we need to advertise MUCs */ +#define ID(...) AddIQIdentity(features, __VA_ARGS__) +#define AD(var) AdvertiseIQFeature(features, var) + ID("gateway", NULL, "matrix", "Parsee MUC gateway"); + ID("conference", NULL, "text", "Parsee MUC gateway"); + ID("component", NULL, "generic", "Parsee component"); + + AD("http://jabber.org/protocol/muc"); +#undef AD +#undef ID + } + else + { +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + } + return features; +} diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 559ffcb..f68b887 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -65,6 +65,12 @@ XMPPDispatcher(void *argp) Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); } + if (ManageMUCStanza(args->muc, stanza)) + { + XMLFreeElement(stanza); + continue; + } + if (StrEquals(stanza->name, "presence")) { PresenceStanza(args, stanza, thread); diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 396fec4..17092b0 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -115,33 +115,37 @@ end: #define DISCO "http://jabber.org/protocol/disco#info" static XMLElement * -IQGenerateQuery(void) +IQGenerateQuery(IQFeatures *features) { - XMLElement *query = XMLCreateTag("query"); + XMLElement *query; + if (!features) + { + return NULL; + } + 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); + size_t i; + for (i = 0; i < ArraySize(features->identity); i++) + { + XMPPIdentity *identity = ArrayGet(features->identity, i); - IQ_ADVERT -#undef AdvertiseSimple + feature = XMLCreateTag("identity"); + XMLAddAttr(feature, "category", identity->category); + XMLAddAttr(feature, "type", identity->type); + XMLAddAttr(feature, "name", identity->name); + + XMLAddChild(query, feature); + } + for (i = 0; i < ArraySize(features->adverts); i++) + { + char *var = ArrayGet(features->adverts, i); + + feature = XMLCreateTag("feature"); + XMLAddAttr(feature, "var", var); + XMLAddChild(query, feature); + } } return query; @@ -151,7 +155,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; - IQFeatures *features = CreateIQFeatures(); + IQFeatures *features; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); @@ -164,13 +168,8 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "id", id); - query = IQGenerateQuery(); -#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); - IQ_IDENTITY -#undef IdentitySimple -#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); - IQ_ADVERT -#undef AdvertiseSimple + features = LookupJIDFeatures(to); + query = IQGenerateQuery(features); { char *ver = XMPPGenerateVer(features); char *node = StrConcat(3, REPOSITORY, "#", ver); @@ -599,10 +598,14 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } else { + char *buf = NULL; + Stream *s = StrStreamWriter(&buf); Log(LOG_WARNING, "Unknown I/Q received:"); - XMLEncode(StreamStdout(), stanza); - StreamPrintf(StreamStdout(),"\n"); - StreamFlush(StreamStdout()); + XMLEncode(s, stanza); + StreamFlush(s); + StreamClose(s); + Log(LOG_WARNING, "%s", buf); + Free(buf); } } diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 8602621..efdd300 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -74,6 +74,14 @@ void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *n void AdvertiseIQFeature(IQFeatures *, char *feature); void FreeIQFeatures(IQFeatures *); +/** + * Looks up the features supported by a JID + * ---------------------- + * Returns: an IQ featureset[LA:HEAP] | NULL + * Thrasher: FreeIQFeatures + * Modifies: NOTHING */ +IQFeatures * LookupJIDFeatures(char *jid); + /** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. * -------- * Returns: A base64 encoded ver hash[LA:HEAP] diff --git a/src/include/MUCServ.h b/src/include/MUCServ.h new file mode 100644 index 0000000..f9a2e4b --- /dev/null +++ b/src/include/MUCServ.h @@ -0,0 +1,39 @@ +#ifndef PARSEE_MUCSERV_H +#define PARSEE_MUCSERV_H + +/*-* An easy interface for handling MUCs. + */ + +typedef struct MUCServer MUCServer; + +#include + +#include + +/** Creates a MUC server handle given a ParseeData handle. + * This handle shall be used for all MUC-related operations. + * ---------------------------------------------------------- + * Returns: a MUC server handle[HEAP] | NULL + * Thrasher: FreeMUCServer + * See-Also: https://xmpp.org/extensions/xep-0045.html */ +extern MUCServer * CreateMUCServer(ParseeData *data); + +/** Manages a stanza from a MUC, and returns true if the stanza + * was actually processed by the MUC server. + * ------------------------------------------------------------- + * See-Also: CreateMUCServer */ +extern bool ManageMUCStanza(MUCServer *serv, XMLElement *stanza); + +/** Checks if a MUC(from its localpart, so + * '#foobar@bridge.blow.hole' -> 'foobar') exists. + * -------------- + * Returns: whenever the MUC exists */ +extern bool MUCServerExists(MUCServer *serv, char *muc); + +/** Destroys all memory and references from a MUC server handle. + * -------------- + * Thrashes: CreateMUCServer + * See-Also: https://xmpp.org/extensions/xep-0045.html */ +extern void FreeMUCServer(MUCServer *serv); + +#endif diff --git a/src/include/Matrix.h b/src/include/Matrix.h index a408389..fb0b5f7 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -2,6 +2,7 @@ #define PARSEE_MATRIX_H #include +#include #include "FileInfo.h" @@ -20,6 +21,12 @@ typedef struct UserID { * Thrasher: Free */ extern UserID * MatrixParseID(char *user); +/** Attempts to parse a Matrix ID from a matrix.to URL. + * ------------------------------------------------- + * Returns: a valid user ID[HEAP] | NULL + * Thrasher: Free */ +extern UserID * MatrixParseIDFromMTO(Uri *uri); + /* Creates an error message JSON, with the specified code and message. */ extern HashMap * MatrixCreateError(char *err, char *msg); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 30c8b61..228bf27 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -18,6 +18,8 @@ typedef struct ParseeData ParseeData; #include #include +#include + #define PARSEE_VERBOSE_NONE 0 #define PARSEE_VERBOSE_LOG 1 #define PARSEE_VERBOSE_TIMINGS 2 @@ -62,6 +64,7 @@ typedef struct ParseeData { HttpServer *server; XMPPComponent *jabber; + MUCServer *muc; Db *db; char *id; From 86deab29afefeac7f16e7227323de831a2e1e09c Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 28 Dec 2024 20:17:03 +0100 Subject: [PATCH 04/45] [FIX] Properly handle new contents with Matrix No more ugly asterisks. It annoyed me enough. --- src/MatrixEventHandler.c | 8 +++++++- src/Parsee/Utils/Formatting.c | 31 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 3d6a69e..1202484 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -370,6 +370,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) DbRef *ref = NULL; HashMap *json; + char *unedited_id = MatrixGetEdit(event); char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); @@ -381,12 +382,17 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *unauth = NULL; char *origin_id = NULL, *stanza = NULL; char *sender = NULL; - char *unedited_id = MatrixGetEdit(event); char *url = GrabString(event, 2, "content", "url"); char *encoded_from = NULL; bool direct = false; + if (unedited_id) + { + char *new_content = GrabString(event, 3, "content", "m.new_content", "body"); + if (new_content) body = new_content; + } + if (ParseeIsPuppet(data->config, m_sender) || ParseeManageBan(data, m_sender, id)) { diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 5a2c6b8..2abf3b5 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -207,6 +207,32 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) } return xepd; } +static char * +GetRawBody(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + char *new = GrabString(event, 3, "content", "m.new_content", "body"); + if (new) + { + return new; + } + } + return GrabString(event, 2, "content", "body"); +} +static char * +GetHTMLBody(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + char *new = GrabString(event, 3, "content", "m.new_content", "formatted_body"); + if (new) + { + return new; + } + } + return GrabString(event, 2, "content", "formatted_body"); +} char * ParseeXMPPify(HashMap *event) { @@ -231,11 +257,12 @@ ParseeXMPPify(HashMap *event) if (!StrEquals(format, "org.matrix.custom.html")) { /* Settle for the raw body instead. */ - char *body = JsonValueAsString(JsonGet(event, 2, "content", "body")); + char *body = GetRawBody(event); return StrDuplicate(body); } - html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body")); + html = GetHTMLBody(event); + html = StrConcat(3, "", html, ""); elem = XMLCDecode(StrStreamReader(html), true, true); From c365681fcbd5af5a411ea8004a63a5ce82592836 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 3 Jan 2025 15:01:35 +0100 Subject: [PATCH 05/45] [MOD] Notify MUCs about errors --- src/Main.c | 43 +++++++++++++++++++++++++++------------- src/Parsee/Data.c | 3 --- src/Parsee/User.c | 3 +-- src/Routes/Media.c | 2 +- src/Routes/Ping.c | 2 -- src/XMPP/MUCServ.c | 1 + src/XMPPCommands/MUCKV.c | 6 +++--- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Main.c b/src/Main.c index 633e117..47a9576 100644 --- a/src/Main.c +++ b/src/Main.c @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -66,21 +66,36 @@ ParseeCheckMatrix(void *datp) if (!ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); - if (++streak >= 5) + if (++streak == 10) { - Log(LOG_ERR, "This has been at least the fifth time in a row."); - Log(LOG_ERR, "Please check if your homeserver is active."); - Log(LOG_ERR, "%s shall now exit...", NAME); + DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); + HashMap *json = DbJson(ref); + HashMap *mucs = GrabObject(json, 1, "mucs"); + char *muc; + void *ignored; - /* TODO: SEGV! */ - pthread_mutex_lock(&data->halt_lock); - data->halted = true; - pthread_mutex_unlock(&data->halt_lock); - XMPPFinishCompStream(data->jabber); - //pthread_join(xmpp_thr, NULL); - Log(LOG_INFO, "Stopping server..."); - HttpServerStop(data->server); + /* Notify any potential MUCs about this */ + while (HashMapIterate(mucs, &muc, &ignored)) + { + char *id = StrRandom(32); + char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee"); + StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id); + SetStanzaType(b, "groupchat"); + SetStanzaBody(b, + "This bridge hasn't been able to reach the Matrix host, and " + "as such, some messages may not have been sent over." + ); + + WriteoutStanza(b, data->jabber, 0); + DestroyStanzaBuilder(b); + + Free(sender); + Free(id); + } + (void) ignored; + + DbUnlock(data->db, ref); } return; } @@ -109,7 +124,7 @@ Main(Array *args, HashMap *env) ); ParseePrintASCII(); Log(LOG_INFO, "======================="); - Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors"); + Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); Log(LOG_INFO, "(This program is free software, see LICENSE.)"); #ifdef PLATFORM_IPHONE diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 68ddf3c..46d1433 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -132,7 +132,6 @@ ParseeCleanup(void *datp) char *chat; size_t i; uint64_t ts = UtilTsMillis(); - size_t entries = 0; chats = DbList(data->db, 1, "chats"); @@ -176,7 +175,6 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ DbDelete(data->db, 4, "chats", chat, #field"s", field); \ - entries++; \ } \ Free(field); \ } \ @@ -233,7 +231,6 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ JsonValueFree(HashMapDelete(field##s, field)); \ - entries++; \ } \ Free(field); \ } \ diff --git a/src/Parsee/User.c b/src/Parsee/User.c index e77f20a..31ce6ff 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -538,7 +538,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id) return ret; } - void ParseeSendPresence(ParseeData *data) { @@ -562,7 +561,7 @@ ParseeSendPresence(ParseeData *data) uint64_t ts = GrabInteger(DbJson(chat), 1, "ts"); int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1; /* Make a fake user join the MUC */ - Log(LOG_NOTICE, "Sending presence to %s last=%ds", rev, diff); + Log(LOG_NOTICE, "Sending presence to %s", rev); XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false); DbUnlock(data->db, chat); diff --git a/src/Routes/Media.c b/src/Routes/Media.c index 004a854..498eba2 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -48,7 +48,7 @@ RouteHead(RouteMedia, arr, argp) HashMap *reqh, *params; char *server = ArrayGet(arr, 0); char *identi = ArrayGet(arr, 1); - char *path, *key, *val; + char *key, *val; char *hmac, *chkmak = NULL; params = HttpRequestParams(args->ctx); diff --git a/src/Routes/Ping.c b/src/Routes/Ping.c index a508336..1d4191e 100644 --- a/src/Routes/Ping.c +++ b/src/Routes/Ping.c @@ -10,8 +10,6 @@ RouteHead(RoutePing, arr, argp) ParseeHttpArg *args = argp; HashMap *request = NULL; HashMap *response = NULL; - Array *events; - size_t i; response = ASVerifyRequest(args); if (response) diff --git a/src/XMPP/MUCServ.c b/src/XMPP/MUCServ.c index 66f4840..557c4a2 100644 --- a/src/XMPP/MUCServ.c +++ b/src/XMPP/MUCServ.c @@ -123,6 +123,7 @@ ManageMUCStanza(MUCServer *serv, XMLElement *stanza) /* TODO: Verify the destination, and make sure we aren't doing * anything stupid(especially with discovery) */ + (void) ret; return false; } diff --git a/src/XMPPCommands/MUCKV.c b/src/XMPPCommands/MUCKV.c index 6dba478..80ac9c4 100644 --- a/src/XMPPCommands/MUCKV.c +++ b/src/XMPPCommands/MUCKV.c @@ -18,9 +18,9 @@ void MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) { ParseeData *data = XMPPGetManagerCookie(m); - char *affiliation, *role; - char *chat_id; - char *muc; + char *affiliation = NULL, *role = NULL; + char *chat_id = NULL; + char *muc = NULL; char *key = NULL, *val = NULL; From c433e314610e22d1b145b83a8979ae58e194397d Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 3 Jan 2025 16:00:24 +0100 Subject: [PATCH 06/45] [DEL] Please enter a valid command Please enter a valid command --- src/MatrixEventHandler.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 1202484..aab9f25 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -260,11 +260,6 @@ ParseeBotHandler(ParseeData *data, HashMap *event) if (*body != '!') { /* All commands are to be marked with a ! */ - Free(ASSend( - data->config, id, profile, - "m.room.message", - MatrixCreateNotice("Please enter a valid command"), 0 - )); Free(profile); return; } From 5e1931a19f8954628287802e9f15a26145001d7b Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 6 Jan 2025 17:09:36 +0000 Subject: [PATCH 07/45] [MOD] Change media path, connect to component through another address --- README.MD | 3 +-- src/HttParsee.c | 2 +- src/Main.c | 3 ++- src/Parsee/Config.c | 10 ++++++++++ src/Parsee/User.c | 2 +- src/XMPP/Component.c | 13 +++++++++++-- src/include/Parsee.h | 2 ++ src/include/Routes.h | 2 +- src/include/XMPP.h | 2 +- 9 files changed, 30 insertions(+), 9 deletions(-) diff --git a/README.MD b/README.MD index 9a7fc6f..ce51662 100644 --- a/README.MD +++ b/README.MD @@ -18,8 +18,7 @@ A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solutio and maybe as a testing ground for Cytoplasm features I sometimes add. (Well, I'm *trying* to do that, at least. -Please scream at me if that fails(or just doesn't run on a overclocked Raspberry -Pi 4B, which, by the way, is literally where Parsee+XMPP is running for now.)) +Please scream at me if that fails(or just doesn't run on a overclocked Raspberry Pi 4B)) ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" diff --git a/src/HttParsee.c b/src/HttParsee.c index 724f9ff..572487c 100644 --- a/src/HttParsee.c +++ b/src/HttParsee.c @@ -73,7 +73,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path ctx = HttpRequest( meth, - HTTP_FLAG_TLS, + conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, conf->homeserver_port, conf->homeserver_host, path ); diff --git a/src/Main.c b/src/Main.c index 47a9576..3e740ed 100644 --- a/src/Main.c +++ b/src/Main.c @@ -223,9 +223,11 @@ Main(Array *args, HashMap *env) Log(LOG_NOTICE, "Connecting to XMPP..."); jabber = XMPPInitialiseCompStream( + parsee_conf->component_addr, parsee_conf->component_host, parsee_conf->component_port ); + Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber); if (!XMPPAuthenticateCompStream( jabber, parsee_conf->shared_comp_secret @@ -290,7 +292,6 @@ Main(Array *args, HashMap *env) { char *parsee = ParseeMXID(conf.handlerArgs); - /* TODO: An hardcoded avatar like this sucks. */ ASSetAvatar(parsee_conf, parsee, "mxc://tedomum.net/" diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 0898063..3b4ce45 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -44,6 +44,9 @@ ParseeConfigLoad(char *conf) #define CopyToInt(to, str) config->to = (int) ( \ JsonValueAsInteger(HashMapGet(json, str)) \ ) +#define CopyToBool(to, str) config->to = (int) ( \ + JsonValueAsBoolean(HashMapGet(json, str)) \ + ) config->http_threads = 8; config->xmpp_threads = 8; @@ -58,8 +61,14 @@ ParseeConfigLoad(char *conf) CopyToStr(server_base, "hs_base"); CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); + CopyToBool(homeserver_tls, "hs_tls"); + if (!HashMapGet(json, "hs_tls")) + { + config->homeserver_tls = true; + } CopyToInt(component_port, "component_port"); + CopyToStr(component_addr, "component_addr"); CopyToStr(component_host, "component_host"); CopyToStr(shared_comp_secret, "shared_secret"); CopyToInt(max_stanza_size, "max_stanza_size"); @@ -129,6 +138,7 @@ ParseeConfigFree(void) return; } Free(config->component_host); + Free(config->component_addr); Free(config->shared_comp_secret); Free(config->db_path); Free(config->homeserver_host); diff --git a/src/Parsee/User.c b/src/Parsee/User.c index 31ce6ff..5a945ea 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -692,7 +692,7 @@ ParseeToUnauth(ParseeData *data, char *mxc) Uri *url = NULL; char *ret; char *key, *hmac; -#define PAT "%s/_matrix/client/v1/media/download/%s%s?hmac=%s" +#define PAT "%s/media/%s%s?hmac=%s" size_t l; if (!data || !mxc) { diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 42e2992..840bec2 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -19,7 +19,7 @@ #define DEFAULT_PROSODY_PORT 5347 XMPPComponent * -XMPPInitialiseCompStream(char *host, int port) +XMPPInitialiseCompStream(char *addr, char *host, int port) { int sd = -1; struct addrinfo hints, *res, *res0; @@ -28,12 +28,17 @@ XMPPInitialiseCompStream(char *host, int port) Stream *stream; XMPPComponent *comp; + if (!addr) + { + addr = host; + } + snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - error = getaddrinfo(host, serv, &hints, &res0); + error = getaddrinfo(addr, serv, &hints, &res0); if (error) { const char *error_str = gai_strerror(error); @@ -66,6 +71,10 @@ XMPPInitialiseCompStream(char *host, int port) if (sd < 0) { + Log(LOG_ERR, + "%s: cannot connect to '%s': no socket available", __func__, + host + ); return NULL; } freeaddrinfo(res0); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 228bf27..a2c73d2 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -41,9 +41,11 @@ typedef struct ParseeConfig { /* Homeserver port info */ char *homeserver_host; int homeserver_port; + int homeserver_tls; /* ------- JABBER -------- */ + char *component_addr; char *component_host; char *shared_comp_secret; int component_port; diff --git a/src/include/Routes.h b/src/include/Routes.h index 8d233e6..cfeea4f 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -75,7 +75,7 @@ typedef struct ParseeCmdArg { X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \ X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \ X_ROUTE("/_matrix/app/v1/ping", RoutePing) \ - X_ROUTE("/_matrix/client/v1/media/download/(.*)/(.*)", RouteMedia) + X_ROUTE("/media/(.*)/(.*)", RouteMedia) #define X_ROUTE(path, name) extern void * name(Array *, void *); ROUTES diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 92a5d80..22e98f6 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -22,7 +22,7 @@ typedef struct XMPPComponent { /* Initialises a raw component stream to host, with an optional port. * If said port is 0, then it is set to the default Prosody port */ -extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port); +extern XMPPComponent * XMPPInitialiseCompStream(char *addr, char *host, int port); /* Authenticates a component stream with a given shared secret, * with a stream ID from the server. This should be called right From 0facbaa5e5e4c0d90fd9742a4b4f86f48b9ef7d6 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 7 Jan 2025 15:14:47 +0000 Subject: [PATCH 08/45] [ADD/FIX/WIP] Allow reverting noflys, ignore MUC DMs (for now) --- src/Commands/BanUser.c | 19 +++++++++++++++++++ src/MatrixEventHandler.c | 14 +++++++------- src/Parsee/Utils/Nofly.c | 22 ++++++++++++++++++++++ src/XMPPThread/Stanzas/Message.c | 17 ++++++++++++++--- src/include/Parsee.h | 9 ++++++++- src/include/Routes.h | 4 ++++ 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/Commands/BanUser.c b/src/Commands/BanUser.c index adc505e..dd380ed 100644 --- a/src/Commands/BanUser.c +++ b/src/Commands/BanUser.c @@ -28,6 +28,25 @@ CommandHead(CmdBanUser, cmd, argp) BotDestroy(); } +CommandHead(CmdNoFlyListDel, cmd, argp) +{ + ParseeCmdArg *args = argp; + ParseeData *data = args->data; + HashMap *event = args->event; + char *user = HashMapGet(cmd->arguments, "user"); + BotInitialise(); + + if (!user) + { + BotDestroy(); + return; + } + + ReplySprintf("Unbanning %s", user); + ParseeGlobalUnban(data, user); + + BotDestroy(); +} CommandHead(CmdNoFlyList, cmd, argp) { ParseeCmdArg *args = argp; diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index aab9f25..89e342d 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -291,10 +291,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) char *room_id = GrabString(event, 1, "room_id"); char *matrix_sender = GrabString(event, 1, "sender"); char *chat_id = NULL, *muc_id = NULL; - char *user; + char *user = NULL; - DbRef *room_data; - HashMap *data_json; + DbRef *room_data = NULL; + HashMap *data_json = NULL; bool direct = false; if (!data || !event || !from || !to) @@ -326,8 +326,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) } else { - char *matrix_name, *matrix_avatar; - char *mime, *sha; + char *matrix_name = NULL, *matrix_avatar = NULL; + char *mime = NULL, *sha = NULL; muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) @@ -361,9 +361,9 @@ static void ParseeMessageHandler(ParseeData *data, HashMap *event) { XMPPComponent *jabber = data->jabber; - StanzaBuilder *builder; + StanzaBuilder *builder = NULL; DbRef *ref = NULL; - HashMap *json; + HashMap *json = NULL; char *unedited_id = MatrixGetEdit(event); char *body = GrabString(event, 2, "content", "body"); diff --git a/src/Parsee/Utils/Nofly.c b/src/Parsee/Utils/Nofly.c index 8a82c0f..439c7d0 100644 --- a/src/Parsee/Utils/Nofly.c +++ b/src/Parsee/Utils/Nofly.c @@ -6,6 +6,28 @@ #include #include +void +ParseeGlobalUnban(ParseeData *data, char *glob) +{ + DbRef *ref; + HashMap *j; + if (!data || !glob) + { + return; + } + + ref = DbLock(data->db, 1, "global_bans"); + if (!ref) + { + ref = DbCreate(data->db, 1, "global_bans"); + } + + j = DbJson(ref); + + JsonValueFree(HashMapDelete(j, glob)); + + DbUnlock(data->db, ref); +} void ParseeGlobalBan(ParseeData *data, char *glob, char *reason) { diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 3c57e69..5497f19 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -394,11 +394,17 @@ end_error: bool media_enabled = chat_id ? ParseeIsMediaEnabled(args, chat_id) : true ; - + bool is_parsee; + /* Note that chat_id still has meaningful info as a boolean + * despite being freed */ Free(chat_id); - pthread_mutex_unlock(&thr->info->chk_lock); + parsee = ParseeJID(args); + is_parsee = StrEquals(to, parsee); + Free(parsee); + parsee = NULL; + LazyRegister(args, encoded, !chat ? res : NULL); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { @@ -411,7 +417,12 @@ end_error: /* Check if it is a media link */ oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); - if (oob && data && media_enabled) + if (chat_id && StrEquals(type, "chat") && !is_parsee) + { + /* Very clearly a MUC DM. */ + event_id = NULL; + } + else if (oob && data && media_enabled) { char *mxc, *mime = NULL; HashMap *content = NULL; diff --git a/src/include/Parsee.h b/src/include/Parsee.h index a2c73d2..8bc4429 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -397,10 +397,17 @@ extern void ParseeDestroyNickTable(void); * to ban them from rooms where Parsee has the ability to do so ("noflying"). * --------------- * Returns: NOTHING - * See-Also: ParseeManageBan + * See-Also: ParseeManageBan, ParseeGlobalUnban * Modifies: the database */ extern void ParseeGlobalBan(ParseeData *, char *user, char *reason); +/** Revokes a user/room/MUC's nofly status + * --------------- + * Returns: NOTHING + * See-Also: ParseeManageBan, ParseeGlobalBan + * Modifies: the database */ +extern void ParseeGlobalUnban(ParseeData *, char *user); + /** Verifies if a user was banned globally. If so (and if {room} is set), * tries to ban the user from it. * --------------- diff --git a/src/include/Routes.h b/src/include/Routes.h index cfeea4f..b927eff 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -27,6 +27,10 @@ typedef struct ParseeCmdArg { "ban-list", CmdNoFlyList, \ "Globally bans a user from using Parsee" \ ) \ + X_COMMAND( \ + "unban-list", CmdNoFlyListDel, \ + "Globally unbans a user from using Parsee" \ + ) \ X_COMMAND( \ "nuke-muc", CmdUnlinkMUC, \ "Removes a MUC. Users should then run " \ From 389870c5d361597023b324e0c71a49541e7f9509 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 25 Jan 2025 12:20:47 +0000 Subject: [PATCH 09/45] [FIX] Ignore unavailable statuses They aren't that useful, especially in MUCs. --- etc/man/man7/.parsee-bridge-guidebook.7.swp | Bin 0 -> 12288 bytes etc/man/man7/.parsee-cmd-syntax.7.swp | Bin 0 -> 12288 bytes etc/man/man7/parsee-bridge-guidebook.7 | 70 ++++++++++++++++++++ src/MatrixEventHandler.c | 18 +++-- src/Parsee/Utils/Formatting.c | 4 +- src/Unistr.c | 10 +++ src/XMPPThread/Stanzas/IQ.c | 5 +- src/XMPPThread/Stanzas/Presence.c | 4 +- src/include/Unistring.h | 6 ++ 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 etc/man/man7/.parsee-bridge-guidebook.7.swp create mode 100644 etc/man/man7/.parsee-cmd-syntax.7.swp create mode 100644 etc/man/man7/parsee-bridge-guidebook.7 diff --git a/etc/man/man7/.parsee-bridge-guidebook.7.swp b/etc/man/man7/.parsee-bridge-guidebook.7.swp new file mode 100644 index 0000000000000000000000000000000000000000..50976eabdb0b742fe8fbb5be8a1dc7c97357d1b7 GIT binary patch literal 12288 zcmeHN&5ImG6fYIOjf$T1ATKMJ1a^88KZwS!Y<9?+_o|nz z(ZNT0YjjU^P~dgF5O?17+T>Mnv-oCsgUAxeS1Acif2}jBbTUz$Nv#t#GUiNl_@7t}V*&$#feSKFyK(34J)3vje#n>ltqXhU#v3*- zI4uka3NJ|NZ~@bwa!aJOMloYy#H; zdw}1r72;RmLm&li0nY6a;wRue;0@pv;6>mC;A!AE@Br}hH9~v^dfc_{f7^TXyXKJbi1^Ca${YHVX7@9Ca!Z; zIKub3NK{2-J<4>98#n@2ERv~cW#u60F%`y<%rY}o35{lynOJ6&$!X;6p6so3`x{*$ zw4;eCRE424(#4j^wv?sZZ0TY`E|rePI#c8f%}iZUYI0>oW@G}hx;XE$s&eAYzp(a`S7XCk5jtrr;PQq^9Kp0b$k0|9Z3@nn%B@)l|f6Jp!jsb*&t z0)!{9J||rzk?48qWrmQBWYDcYsNYph!@Gyc9De%lWcUCJ@Jq(SbyFatw~quvp(5~(e$OM=sAa@=Kj7?UALWa)+iR5^x+!j#JiBS~Y1OLY8fUP7H^Gd7%5Jsg6@$*cQXN zesYztgRLM?32gx!X#Hv^K+t3zmdRPnT=rO16>&8yol5q$l1=p_MHHwplcR%h`TFW^ zJ>f#~o-&8fZ(f>$`;2cJoEe|ZUh_MJAdein{f;}gOtJA;IE8xnXvsut&Z=M{uotO0}h z+=;jrEJDoCSA1c&{<2uK+h@tvqjnSr%MNer9r~<%363FPnDs8R+OP)qkW_9Vns$*L zpS*NICHFD(V9gE9PuR=4h|vxvjVogqg`MjBz7f$NQ)mF37fzPJ*BI`OYhNnR2c~g_ zkVxWRI_ZHbjMR#VIdm959V!dKS&k97OP-gWUDN!y3VeGSiL zmgKt7C{u|o&PD_>nFZeBd;Q_*_0`Q4PKFTWUmrxL4lT3S=sG+0Q6yE})Qg;_qdCFu z4fmJVu)#;e6Z6N5Kb~%YVfTY@=jDCbM$I7 M2BeIpau$i-0os$Jhx%iV+&`&e|jG4|iwX z)UC>o8$EC-f*XSWg1>_^;*7+F0~|PT;5+MesMzP5W0g2|HfKd! z&5whcT_m}>3E5CdXB z42Xf}+(15Cqt7sh7s`1w%KO@5_wrG0Vn7Ut0Wly3#DEwO17bi7hygJm2E@Q~Xg~!- zS6?A|=^T>B|NmEi|NrzV(RV)0QbQs-~@aCZi4He1ulS> z0fE2I6a5AL1b=|v!EfLf@HKb{z5-u@&%sUb9=HbH20tUu@4>g=A$S1pfx939Z-Fb| zP2lGAB9KcAhygJm2E>3E5CdXB42Xfp40wCIJcheoze?Wl1`pe9ZXOH|iH@ema*+D- zSSLB-_dE%7#>8TEQyx=JHRm71x^@_L$omRUy zY!hYL&a|JK+d5j{)gF)5Gg~2*1gnQC^s-~o4=y?*n0 zd&ut}4!GYO6gzX!=HuRe8x6;M*iyWI*ll)tm`{b>T8fm-IW#&7iVaXK#6Tx1)^NEf KR_3X~w)+SCjabber, jid, rev, hash, -1, true) && nonce < 32) { char *nonce_str = StrInt(nonce); char *input = StrConcat(3, sender, name, nonce_str); char *hex = ParseeHMACS(data->id, input); + Unistr *filterASCII = UnistrFilter(uninick, UnistrIsASCII); if (strlen(hex) >= 8) { @@ -45,6 +48,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char Free(nick); Free(rev); + Free(revscii); nick = StrConcat(4, name, "[", hex, "]"); rev = StrConcat(3, muc, "/", nick); @@ -364,13 +368,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) StanzaBuilder *builder = NULL; DbRef *ref = NULL; HashMap *json = NULL; - - char *unedited_id = MatrixGetEdit(event); + + char *m_sender = GrabString(event, 1, "sender"); + char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); - char *m_sender = GrabString(event, 1, "sender"); - char *chat_id, *muc_id; + char *chat_id = NULL, *muc_id = NULL; char *reply_id = MatrixGetReply(event); char *xepd = ParseeXMPPify(event); char *type, *user, *xmppified_user = NULL, *to = NULL; @@ -381,6 +385,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *encoded_from = NULL; bool direct = false; + unedited_id = MatrixGetEdit(event); if (unedited_id) { @@ -455,6 +460,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Free(name); Free(avatar); } + if (reply_id) { /* TODO: Monocles chat DM users HATE this trick! @@ -511,8 +517,8 @@ end: Free(stanza); Free(sender); Free(unauth); - Free(unedited_id); Free(encoded_from); + Free(unedited_id); DbUnlock(data->db, ref); ref = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 2abf3b5..8f41a56 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -210,9 +210,11 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) static char * GetRawBody(HashMap *event) { - if (MatrixGetEdit(event)) + void *id; + if ((id = MatrixGetEdit(event))) { char *new = GrabString(event, 3, "content", "m.new_content", "body"); + Free(id); if (new) { return new; diff --git a/src/Unistr.c b/src/Unistr.c index 5eaae43..a1eb1e4 100644 --- a/src/Unistr.c +++ b/src/Unistr.c @@ -192,6 +192,16 @@ UnistrGetch(Unistr *unistr, size_t i) return i < unistr->length ? unistr->codepoints[i] : 0; } bool +UnistrIsASCII(uint32_t u) +{ + if (u == 0) + { + return NULL; + } + + return u < 0x7F; +} +bool UnistrIsBMP(uint32_t u) { if (u == 0) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 17092b0..af71cca 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -572,7 +572,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) { XMLElement *iq_reply, *query; - XMLElement *name, *version; + XMLElement *name, *version, *os; iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "to", from); @@ -585,12 +585,15 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { name = XMLCreateTag("name"); version = XMLCreateTag("version"); + os = XMLCreateTag("os"); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); + XMLAddChild(os, XMLCreateText(VERSION "POSIX-like")); } XMLAddChild(query, name); XMLAddChild(query, version); + XMLAddChild(query, os); XMLAddChild(iq_reply, query); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); diff --git a/src/XMPPThread/Stanzas/Presence.c b/src/XMPPThread/Stanzas/Presence.c index 65c2082..947fc4b 100644 --- a/src/XMPPThread/Stanzas/Presence.c +++ b/src/XMPPThread/Stanzas/Presence.c @@ -298,7 +298,7 @@ end_item: Free(room); FreeStatuses(statuses); } - if (status) + if (status && !StrEquals(type, "unavailable")) { XMLElement *status_data = ArrayGet(status->children, 0); char *decode_from = ParseeLookupJID(oid); @@ -309,6 +309,8 @@ end_item: status_str = status_data->data; } + + /* 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)." diff --git a/src/include/Unistring.h b/src/include/Unistring.h index 6fbc296..d90d61c 100644 --- a/src/include/Unistring.h +++ b/src/include/Unistring.h @@ -64,6 +64,12 @@ extern void UnistrFree(Unistr *unistr); * Returns: whenever the character is within the BMP */ extern bool UnistrIsBMP(uint32_t u); +/** Returns true IFF the character is within the 7-bit ASCII range + * not 0x0000 + * ------------------------------------------------------------ + * Returns: whenever the character is within ASCII */ +extern bool UnistrIsASCII(uint32_t u); + typedef bool (*UnistrFilterFunc)(uint32_t u); /** "Filters" characters in a Unistring by codepoint, removing * those with callbacks which return false into a new unistring. From f9de7f17504c01759d59e4b581d36a3832cd8c70 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 28 Jan 2025 15:01:57 +0000 Subject: [PATCH 10/45] [MOD/FIX] Be a bit more specific with component errors --- src/MatrixEventHandler.c | 10 ++-------- src/Signal.c | 7 +------ src/XMPP/Component.c | 8 ++++++++ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 60a41c7..bf42124 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -26,20 +26,15 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char char *rev = StrConcat(3, muc, "/", nick); int nonce = 0; - Log(LOG_DEBUG, "MUCJOINER: filtered '%s' to '%s'", name, nick); - UnistrFree(uninick); UnistrFree(filtered); - UnistrFree(filterASCII); - /* TODO: Make sure that we fall back to plain ASCII if it fails too many - * times. */ + /* TODO: vCards! */ while (!XMPPJoinMUC(data->jabber, jid, rev, hash, -1, true) && nonce < 32) { char *nonce_str = StrInt(nonce); char *input = StrConcat(3, sender, name, nonce_str); char *hex = ParseeHMACS(data->id, input); - Unistr *filterASCII = UnistrFilter(uninick, UnistrIsASCII); if (strlen(hex) >= 8) { @@ -48,7 +43,6 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char Free(nick); Free(rev); - Free(revscii); nick = StrConcat(4, name, "[", hex, "]"); rev = StrConcat(3, muc, "/", nick); @@ -368,7 +362,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) StanzaBuilder *builder = NULL; DbRef *ref = NULL; HashMap *json = NULL; - + char *m_sender = GrabString(event, 1, "sender"); char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); diff --git a/src/Signal.c b/src/Signal.c index e9d00e8..3f01dcb 100644 --- a/src/Signal.c +++ b/src/Signal.c @@ -26,11 +26,6 @@ SignalHandler(int signal) HttpServerStop(data->server); return; } - if (signal == SIGPIPE) - { - Log(LOG_DEBUG, "Caught a SIGPIPE..."); - return; - } } bool @@ -46,7 +41,7 @@ ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp) sa.sa_flags = SA_RESTART; #define Register(act) (sigaction(act, &sa, NULL) >= 0) - if (!Register(SIGTERM) || !Register(SIGINT) || !Register(SIGPIPE)) + if (!Register(SIGTERM) || !Register(SIGINT)) { Log(LOG_ERR, "Couldn't register signals..."); return false; diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 840bec2..1bdff53 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -61,6 +61,10 @@ XMPPInitialiseCompStream(char *addr, char *host, int port) if (connect(sd, res->ai_addr, res->ai_addrlen) < 0) { + Log(LOG_ERR, + "%s: cannot connect to '%s': %s", __func__, + host, strerror(errno) + ); close(sd); sd = -1; continue; @@ -82,6 +86,10 @@ XMPPInitialiseCompStream(char *addr, char *host, int port) stream = StreamFd(sd); if (!stream) { + Log(LOG_ERR, + "%s: cannot connect to '%s': %s", __func__, + host, "couldn't create a Cytoplasm stream" + ); close(sd); return NULL; } From b485298fbc6893582ef4623ae6cea7643119fa49 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 29 Jan 2025 19:15:23 +0100 Subject: [PATCH 11/45] [FIX] Log error if the config was not parsed --- src/Parsee/Config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 3b4ce45..46a391f 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -36,6 +36,12 @@ ParseeConfigLoad(char *conf) return; } json = JsonDecode(stream); + if (!json) + { + Log(LOG_ERR, "Could not parse config JSON"); + StreamClose(stream); + return; + } config = Malloc(sizeof(*config)); #define CopyToStr(to, str) config->to = StrDuplicate( \ From 1e7d71f9f61890675a987257592217e32de0dbd2 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 5 Feb 2025 08:54:45 +0100 Subject: [PATCH 12/45] [ADD] Allow people to see component errors --- src/XMPP/Component.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 1bdff53..12f9f9e 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -182,6 +182,49 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared) { Log(LOG_ERR, "Excepted empty handshake reply, got nonsense."); Log(LOG_ERR, "Another service (possibly Parsee) may have taken over."); + while ((ev = XMLCrank(sax))) + { + char *key, *val; + switch (ev->type) + { + case XML_LEXER_STARTELEM: + Log(LOG_DEBUG, "<%s>", ev->element); + + LogConfigIndent(LogConfigGlobal()); + LogConfigIndent(LogConfigGlobal()); + /* TODO: Log out attributes a little better */ + while (HashMapIterate(ev->attrs, &key, (void **) &val)) + { + Log(LOG_DEBUG, "(%s=%s)", key, val); + } + LogConfigUnindent(LogConfigGlobal()); + LogConfigUnindent(LogConfigGlobal()); + + LogConfigIndent(LogConfigGlobal()); + break; + case XML_LEXER_ELEM: + Log(LOG_DEBUG, "<%s/>", ev->element); + LogConfigIndent(LogConfigGlobal()); + LogConfigIndent(LogConfigGlobal()); + /* TODO: Log out attributes a little better */ + while (HashMapIterate(ev->attrs, &key, (void **) &val)) + { + Log(LOG_DEBUG, "(%s=%s)", key, val); + } + LogConfigUnindent(LogConfigGlobal()); + LogConfigUnindent(LogConfigGlobal()); + break; + case XML_LEXER_ENDELEM: + Log(LOG_DEBUG, "", ev->element); + LogConfigIndent(LogConfigGlobal()); + break; + case XML_LEXER_DATA: + Log(LOG_DEBUG, "%s", ev->data); + break; + } + XMLFreeEvent(ev); + } + LogConfigIndentSet(LogConfigGlobal(), 0); Log(LOG_ERR, ""); Log(LOG_ERR, "Simply jealous of that other service..."); Free(stream_id); From 110a1b695f2689b982944971bfb654d3ba43bc1c Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 5 Feb 2025 08:58:25 +0100 Subject: [PATCH 13/45] [FIX] Fix the indenting --- src/XMPP/Component.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 12f9f9e..e3ff4e4 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -215,8 +215,8 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared) LogConfigUnindent(LogConfigGlobal()); break; case XML_LEXER_ENDELEM: + LogConfigUnindent(LogConfigGlobal()); Log(LOG_DEBUG, "", ev->element); - LogConfigIndent(LogConfigGlobal()); break; case XML_LEXER_DATA: Log(LOG_DEBUG, "%s", ev->data); From 4f694129fc9ff297cae318fb53fe238948a5bab1 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 7 Feb 2025 18:34:45 +0000 Subject: [PATCH 14/45] [FIX] Fix silly SEGV Thanks, EP --- src/MatrixEventHandler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index bf42124..fa44d2d 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -255,7 +255,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event) return; } - if (*body != '!') + if (!body || *body != '!') { /* All commands are to be marked with a ! */ Free(profile); From c2536c2e846bb764bcb59ebd42866af81e94a745 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 7 Feb 2025 18:52:58 +0000 Subject: [PATCH 15/45] [FIX] Log out some MUC information --- src/XMPP/MUC.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 67bc9d1..9ecfcf2 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -44,6 +44,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) Free(uuid); if (!iq_query || !StrEquals(iq_query->name, "iq")) { + Log(LOG_ERR, "Didn't receive an stanza"); XMLFreeElement(iq_query); return false; } @@ -55,6 +56,11 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) "conference")) { XMLFreeElement(iq_query); + Log(LOG_DEBUG, "MUC INFO ERROR"); + Log(LOG_DEBUG, + "identityp=%p category=%s", identity, + identity ? HashMapGet(identity->attrs, "category") : NULL + ); return false; } From 1936be0078dcd7a53b35577220bca8228792356a Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 8 Feb 2025 08:45:21 +0000 Subject: [PATCH 16/45] [FIX] Fix minor DB fuckup with tools Used to open a flatfile database whenever it couldn't find an LMDB one, which obviously caused *problems* --- tools/adminify.c | 39 +++++++++++++++++++-------------------- tools/common.h | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/tools/adminify.c b/tools/adminify.c index e87d97e..1fdd6cf 100644 --- a/tools/adminify.c +++ b/tools/adminify.c @@ -89,31 +89,30 @@ Main(Array *args, HashMap *env) glob = ArrayGet(args, 2); parsee = GetDB(db_path); - if (parsee) + if (!parsee) { + Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); + (void) env; + return EXIT_FAILURE; + } - if (glob) - { - Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path); - AddAdmin(parsee, glob); - Log(LOG_INFO, "Successfully added glob %s.", glob); - Log(LOG_INFO, "*I'm jealous of all these admins!*"); - - DbClose(parsee); - return EXIT_SUCCESS; - } - - /* List admins */ - Log(LOG_INFO, "Admin list:"); - LogConfigIndent(LogConfigGlobal()); - ListAdmins(parsee); - LogConfigUnindent(LogConfigGlobal()); + if (glob) + { + Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path); + AddAdmin(parsee, glob); + Log(LOG_INFO, "Successfully added glob %s.", glob); + Log(LOG_INFO, "*I'm jealous of all these admins!*"); DbClose(parsee); return EXIT_SUCCESS; } - Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); - (void) env; - return EXIT_FAILURE; + /* List admins */ + Log(LOG_INFO, "Admin list:"); + LogConfigIndent(LogConfigGlobal()); + ListAdmins(parsee); + LogConfigUnindent(LogConfigGlobal()); + + DbClose(parsee); + return EXIT_SUCCESS; } diff --git a/tools/common.h b/tools/common.h index eee4793..9687ea3 100644 --- a/tools/common.h +++ b/tools/common.h @@ -108,7 +108,7 @@ GetDB(char *config) { ret = DbOpenLMDB(db_path, lmdb_size); } - if (!ret) + else { ret = DbOpen(db_path, 0); } From f94db460ac6459f8ebe94d63f7140c2e40325dcc Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 9 Feb 2025 21:51:38 +0000 Subject: [PATCH 17/45] [FIX/WIP] Fix annoying formatting bug Fixes #16 --- src/Parsee/Utils/Formatting.c | 9 ++ src/XML/Parser.c | 6 ++ src/XML/SAX.c | 37 ++++++++- src/include/XML.h | 2 + tools/plumb.c | 152 ++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 tools/plumb.c diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 8f41a56..17f513f 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -267,6 +268,14 @@ ParseeXMPPify(HashMap *event) html = StrConcat(3, "", html, ""); elem = XMLCDecode(StrStreamReader(html), true, true); + if (!elem) + { + /* Settle for the raw body instead. + * TODO: Have the parser be more leinent on errors in HTML mode. */ + char *body = GetRawBody(event); + Free(html); + return StrDuplicate(body); + } flags.quote = false; xepd = XMPPifyElement(event, elem, flags); diff --git a/src/XML/Parser.c b/src/XML/Parser.c index 506e49d..350f0e4 100644 --- a/src/XML/Parser.c +++ b/src/XML/Parser.c @@ -31,6 +31,12 @@ XMLCDecode(Stream *stream, bool autofree, bool html) bool flag = false; switch (event->type) { + case XML_ERROR: + XMLFreeEvent(event); + XMLFreeElement(ret); + ArrayFree(stack); + XMLFreeLexer(lexer); + return NULL; case XML_LEXER_STARTELEM: /* Create a new element that will populated. */ top = XMLCreateTag(event->element); diff --git a/src/XML/SAX.c b/src/XML/SAX.c index 21430bb..defea6b 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -58,6 +58,7 @@ static char * XMLPopElement(XMLexer *lexer); static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateRelax(XMLexer *lexer); +static XMLEvent * XMLCreateError(XMLexer *lexer); static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end); static XMLEvent * XMLCreateData(XMLexer *lexer); @@ -198,7 +199,9 @@ XMLCrank(XMLexer *lexer) else if (XMLookahead(lexer, "--", false)) { /* Throw error */ - return NULL; + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } break; case XML_STATE_PI: @@ -215,6 +218,9 @@ XMLCrank(XMLexer *lexer) if (!attrname) { /* TODO: Throw error */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } XMLPushElement(lexer, attrname); @@ -241,7 +247,10 @@ XMLCrank(XMLexer *lexer) } else if (XMLookahead(lexer, "'", true)) { - while (true); + //while (true); uh oh + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } break; case XML_STATE_ATTRTAIL: @@ -250,6 +259,8 @@ XMLCrank(XMLexer *lexer) if (!XMLookahead(lexer, ">", true)) { /* TODO: Throw error. */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); break; } lexer->state = XML_STATE_NONE; @@ -258,6 +269,8 @@ XMLCrank(XMLexer *lexer) break; default: /* TODO */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); break; } /* TODO: Crank our XML parser. */ @@ -693,6 +706,26 @@ XMLCreateData(XMLexer *lexer) return event; } static XMLEvent * +XMLCreateError(XMLexer *lexer) +{ + XMLEvent *event = Malloc(sizeof(*event)); + size_t elements = ArraySize(lexer->data.elements); + + event->type = XML_ERROR; + event->element = elements ? + StrDuplicate(ArrayGet(lexer->data.elements, elements - 1)) : + NULL; + event->attrs = NULL; + event->data = NULL; + + /* TODO */ + event->line = 0; + event->col = 0; + event->offset = 0; + + return event; +} +static XMLEvent * XMLCreateRelax(XMLexer *lexer) { XMLEvent *event = Malloc(sizeof(*event)); diff --git a/src/include/XML.h b/src/include/XML.h index 8fd108d..c7b6610 100644 --- a/src/include/XML.h +++ b/src/include/XML.h @@ -13,6 +13,8 @@ typedef struct XMLEvent { XML_LEXER_DATA, XML_LEXER_ELEM, /* Empty attr */ XML_LEXER_ENDELEM, + + XML_ERROR, XML_RELAX } type; diff --git a/tools/plumb.c b/tools/plumb.c new file mode 100644 index 0000000..1bc9e14 --- /dev/null +++ b/tools/plumb.c @@ -0,0 +1,152 @@ +/* plumb.c - Small utility to manage plumbings from a shutoff instance. + * ============================================================ + * TODO: write other commands, and move some code to common.h + * + * Under CC0, as its a rather useful example of a Parsee tool. + * See LICENSE for more information about Parsee's licensing. */ + +#include "common.h" + +#include + +static void +DeletePlumbID(Db *parsee, char *chat_id) +{ + DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id); + DbRef *chats = DbLock(parsee, 1, "chats"); + char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id"); + char *jabber_id = GrabString(DbJson(chat_id_ref), 1, "jabber_id"); + + HashMap *rooms = GrabObject(DbJson(chats), 1, "rooms"); + HashMap *mucs = GrabObject(DbJson(chats), 1, "mucs"); + + JsonValueFree(HashMapDelete(rooms, matrix_id)); + JsonValueFree(HashMapDelete(mucs, jabber_id)); + + DbUnlock(parsee, chat_id_ref); + DbUnlock(parsee, chats); + DbDelete(parsee, 2, "chats", chat_id); +} +static void +DeletePlumb(Db *parsee, char *potential_id) +{ + if (!parsee || !potential_id) + { + return; + } + + if (!strncmp(potential_id, "xmpp:", 5)) + { + DbRef *ref; + HashMap *mucs; + char *chat_id; + /* Try to parse it as an XMPP address */ + potential_id += 5; + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + mucs = GrabObject(DbJson(ref), 1, "mucs"); + chat_id = StrDuplicate(GrabString(mucs, 1, potential_id)); + DbUnlock(parsee, ref); + + DeletePlumbID(parsee, chat_id); + Free(chat_id); + + return; + } + if (*potential_id == '!') + { + /* Try to parse it as a Matrix room ID */ + DbRef *ref; + HashMap *rooms; + char *chat_id; + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + rooms = GrabObject(DbJson(ref), 1, "rooms"); + chat_id = StrDuplicate(GrabString(rooms, 1, potential_id)); + DbUnlock(parsee, ref); + + DeletePlumbID(parsee, chat_id); + Free(chat_id); + + return; + } + + /* Try to parse it as a chat ID */ + DeletePlumbID(parsee, potential_id); +} +static void +ListPlumbs(Db *parsee) +{ + DbRef *ref; + HashMap *mucs; + + char *muc; + JsonValue *value; + if (!parsee) + { + return; + } + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + mucs = GrabObject(DbJson(ref), 1, "mucs"); + while (HashMapIterate(mucs, &muc, (void **) &value)) + { + char *chat_id = JsonValueAsString(value); + DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id); + char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id"); + + /* TODO */ + Log(LOG_INFO, "- Plumb xmpp:%s <=> %s", muc, matrix_id); + Log(LOG_INFO, " - ID=%s", chat_id); + + DbUnlock(parsee, chat_id_ref); + } + + DbUnlock(parsee, ref); +} + +int +Main(Array *args, HashMap *env) +{ + char *db_path, *action, *exec; + int ret = EXIT_SUCCESS; + Db *parsee; + + exec = ArrayGet(args, 0); + + if (ArraySize(args) < 3) + { + Log(LOG_ERR, "Usage: %s [config] [action] ", exec); + return EXIT_FAILURE; + } + + db_path = ArrayGet(args, 1); + action = ArrayGet(args, 2); + + parsee = GetDB(db_path); + if (!parsee) + { + Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); + return EXIT_FAILURE; + } + + if (StrEquals(action, "list") || StrEquals(action, "ls")) + { + ListPlumbs(parsee); + } + else if (StrEquals(action, "del")) + { + if (ArraySize(args) != 4) + { + Log(LOG_ERR, "%s: please show a !roomid:matrix.org, xmpp:mucid@jabber.org, or local hash", exec); + ret = EXIT_FAILURE; + goto end; + } + DeletePlumb(parsee, ArrayGet(args, 3)); + } + + +end: + DbClose(parsee); + return ret; +} From 176f390c4bfd7f624cbce992c411d6ed70cc4716 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 11 Feb 2025 17:18:01 +0000 Subject: [PATCH 18/45] [FIX] Use the puppet's name when pinged if possible --- src/MatrixEventHandler.c | 2 +- src/Parsee/Utils/Formatting.c | 36 +++++++++++++++++++++-------------- src/XMPPThread/ReListener.c | 2 ++ src/include/Parsee.h | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index fa44d2d..2c5c5bd 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -370,7 +370,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *ev_id = GrabString(event, 1, "event_id"); char *chat_id = NULL, *muc_id = NULL; char *reply_id = MatrixGetReply(event); - char *xepd = ParseeXMPPify(event); + char *xepd = ParseeXMPPify(data, event); char *type, *user, *xmppified_user = NULL, *to = NULL; char *unauth = NULL; char *origin_id = NULL, *stanza = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 17f513f..44e255a 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -20,7 +20,7 @@ typedef struct XMPPFlags { bool quote; } XMPPFlags; static char * -XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) +XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags) { char *xepd = NULL, *tmp = NULL; @@ -70,7 +70,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -83,7 +83,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -96,7 +96,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -129,7 +129,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -144,7 +144,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -162,11 +162,19 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) UserID *id = MatrixParseIDFromMTO(pref); if (id) { + char *real_id = StrConcat(4, "@", id->localpart, ":", id->server); /* TODO: Detect if it already is a Parsee user */ - Concat("@"); - Concat(id->localpart); - Concat(":"); - Concat(id->server); + if (ParseeIsPuppet(conf, real_id)) + { + char *name = ASGetName(conf, NULL, real_id); + Concat((name ? name : real_id)); + Free(name); + } + else + { + Concat(real_id); + } + Free(real_id); } else { @@ -180,7 +188,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -196,7 +204,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -237,7 +245,7 @@ GetHTMLBody(HashMap *event) return GrabString(event, 2, "content", "formatted_body"); } char * -ParseeXMPPify(HashMap *event) +ParseeXMPPify(ParseeData *data, HashMap *event) { char *type, *format, *html; char *xepd = NULL; @@ -278,7 +286,7 @@ ParseeXMPPify(HashMap *event) } flags.quote = false; - xepd = XMPPifyElement(event, elem, flags); + xepd = XMPPifyElement(data ? data->config : NULL, event, elem, flags); XMLFreeElement(elem); Free(html); diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index f68b887..28d117b 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -56,6 +56,8 @@ XMPPDispatcher(void *argp) if (!stanza) { + /* TODO: We shouldn't be busywaiting. Even with a sleep call. + */ UtilSleepMillis(10); continue; } diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 8bc4429..92aacec 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -356,7 +356,7 @@ extern int ParseeFindDatastartU(char *data); /* XMPP-ifies a message event to XEP-0393 if possible. */ -extern char * ParseeXMPPify(HashMap *event); +extern char * ParseeXMPPify(ParseeData *data, HashMap *event); /* Finds an event ID from an ID in the stanza's attributes */ extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id); From 1c51d573558ca1c591f660325e79c6495fa390ae Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 08:24:18 +0000 Subject: [PATCH 19/45] [ADD] Add accept_pings parameters This is off by default, but highly encouraged for Synapse deployments --- .gitignore | 6 ++++-- CHANGELOG.md | 13 +++++++++++++ build.conf | 4 ++-- src/Main.c | 2 +- src/Parsee/Config.c | 1 + src/include/Parsee.h | 2 ++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 05919ab..5f7d56c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,11 @@ parsee* parsee *.swp .* -data -data/* +data* +data*/* Makefile configure +gmon.out tools/out tools/out/* @@ -24,3 +25,4 @@ tags !.forgejo !.forgejo/* !.forgejo/** + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5999d7f..04358d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,19 @@ commit done between releases. *There is currently no beta releases of Parsee* ## Alpha + +### v0.3.0[lunar-rainbow] +This is the first release of 2025! +TBD +#### New things +- Allow admins to remove users from the nofly list. +- Parsee can now access the homeserver/component locally (rather than over the network) +- The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' +- Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. +#### Bugfixes +- Fix potential infinite loops when processing some messages. +- Parsee now handles pinging puppets from Matrix more sanely. + ### v0.2.0[star-of-hope] <8/11/2024> Fixes some media metadata things, replaces the build system, tries out avatar support some more, MUC contexts, and speeds diff --git a/build.conf b/build.conf index 9ebf253..a2e17c1 100644 --- a/build.conf +++ b/build.conf @@ -1,6 +1,6 @@ -CODE=star-of-hope +CODE=lunar-rainbow NAME=Parsee -VERSION=0.2.0 +VERSION=0.3.0 BINARY=parsee SOURCE=src INCLUDES=src/include diff --git a/src/Main.c b/src/Main.c index 3e740ed..f596126 100644 --- a/src/Main.c +++ b/src/Main.c @@ -63,7 +63,7 @@ ParseeCheckMatrix(void *datp) { static volatile uint64_t streak = 0; ParseeData *data = datp; - if (!ASPing(data->config)) + if (data->config->accept_pings && !ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); if (++streak == 10) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 46a391f..fa4b96e 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -68,6 +68,7 @@ ParseeConfigLoad(char *conf) CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); CopyToBool(homeserver_tls, "hs_tls"); + CopyToBool(homeserver_tls, "accept_pings"); if (!HashMapGet(json, "hs_tls")) { config->homeserver_tls = true; diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 92aacec..5726286 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -43,6 +43,8 @@ typedef struct ParseeConfig { int homeserver_port; int homeserver_tls; + bool accept_pings; + /* ------- JABBER -------- */ char *component_addr; From 7f396a037927330d0063a9bfffcebd76b73457f3 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 08:33:05 +0000 Subject: [PATCH 20/45] [FIX] Write config to the proper field! --- src/Parsee/Config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index fa4b96e..45e6fe5 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -68,11 +68,11 @@ ParseeConfigLoad(char *conf) CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); CopyToBool(homeserver_tls, "hs_tls"); - CopyToBool(homeserver_tls, "accept_pings"); if (!HashMapGet(json, "hs_tls")) { config->homeserver_tls = true; } + CopyToBool(accept_pings, "accept_pings"); CopyToInt(component_port, "component_port"); CopyToStr(component_addr, "component_addr"); From 9a2d4188e2d3886d600d9a6dfe77182bfe6bca9b Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 16:43:43 +0000 Subject: [PATCH 21/45] [FIX] Do not put version in the OS field --- src/XMPPThread/Stanzas/IQ.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index af71cca..1b55bf4 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -589,7 +589,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); - XMLAddChild(os, XMLCreateText(VERSION "POSIX-like")); + XMLAddChild(os, XMLCreateText("POSIX-like")); } XMLAddChild(query, name); XMLAddChild(query, version); From 71f3836ee1dc0c7056156b9e1ec3386df53f5132 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 16:47:48 +0000 Subject: [PATCH 22/45] [ADD] Use actual OS as information --- src/XMPPThread/Stanzas/IQ.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 1b55bf4..5dc9bc3 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -583,13 +584,15 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", "jabber:iq:version"); { + struct utsname info; name = XMLCreateTag("name"); version = XMLCreateTag("version"); os = XMLCreateTag("os"); + uname(&info); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); - XMLAddChild(os, XMLCreateText("POSIX-like")); + XMLAddChild(os, XMLCreateText(info.sysname)); } XMLAddChild(query, name); XMLAddChild(query, version); From 3bef6afa5dae72d34996c891c225fb3edd8985a7 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 19:10:26 +0000 Subject: [PATCH 23/45] [FIX/WIP] Try escaping room IDs First attempt at dealing with #20 --- src/AS/Send.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AS/Send.c b/src/AS/Send.c index 766f393..a787c53 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -47,11 +47,13 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u ts_str = TSToStr(ts); txn = StrRandom(16); + id = HttpUrlEncode(id); path = StrConcat(11, "/_matrix/client/v3/rooms/", id, "/send/", type, "/", txn, "?", "user_id=", user, "&ts=", ts_str ); + Free(id); Free(txn); Free(ts_str); From 43175e32e52fc14bfa64203ad0123658146001e6 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 20:28:49 +0000 Subject: [PATCH 24/45] [FIX/WIP] Log out error info on ASSend --- src/AS/Send.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/AS/Send.c b/src/AS/Send.c index a787c53..9b81a71 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -33,6 +33,11 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u char *path; char *txn, *ret; char *ts_str; + HttpStatus status; + if (!ret) + { + Log(LOG_ERR, "%", ret); + } HashMap *reply; if (!conf || !id || !type || !user || !c) { @@ -60,10 +65,17 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u ctx = ParseeCreateRequest(conf, HTTP_PUT, path); Free(path); ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, c); + status = ParseeSetRequestJSON(ctx, c); reply = JsonDecode(HttpClientStream(ctx)); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); + if (!ret) + { + Log(LOG_ERR, "Got %s from HTTP", HttpStatusToString(status)); + JsonEncode(reply, StreamStdout(), JSON_PRETTY); + StreamPrintf(StreamStdout(), "\n"); + StreamFlush(StreamStdout()); + } JsonFree(reply); HttpClientContextFree(ctx); From d12255b2265b9cc79891d53e1740ab6071a89484 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 21:58:42 +0000 Subject: [PATCH 25/45] [FIX/WIP] Do not use room ID for plumbing --- src/Commands/Plumb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Plumb.c b/src/Commands/Plumb.c index f51e4e6..bee94f2 100644 --- a/src/Commands/Plumb.c +++ b/src/Commands/Plumb.c @@ -59,7 +59,7 @@ CommandHead(CmdPlumb, cmd, argp) goto end; } - chat_id = ParseePushMUC(args->data, room_id, muc); + chat_id = ParseePushMUC(args->data, room, muc); // ew. if (chat_id) { char *rev = StrConcat(2, muc, "/parsee"); From e2b014d00056d8e39a5c277e8d286249a16d44e9 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 15 Feb 2025 08:44:25 +0000 Subject: [PATCH 26/45] [FIX/WIP] Fix replies and try fixing a bug with MUCs --- src/AS/Room.c | 18 +++++++++-- src/Main.c | 1 + src/MatrixEventHandler.c | 4 +-- src/Parsee/User.c | 51 ++++++++++++++++++++++++-------- src/Unistr.c | 35 ++++++++++++++++++++++ src/XMPP/Stanza.c | 25 ++++++++++++++++ src/XMPPThread/Stanzas/Message.c | 14 ++++++++- src/include/Parsee.h | 2 +- src/include/Routes.h | 3 +- src/include/Unistring.h | 6 ++++ src/include/XMPP.h | 3 ++ 11 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/AS/Room.c b/src/AS/Room.c index 9e10579..e9691e1 100644 --- a/src/AS/Room.c +++ b/src/AS/Room.c @@ -25,11 +25,13 @@ ASInvite(const ParseeConfig *conf, char *id, char *invited) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/invite", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -60,11 +62,13 @@ ASBan(const ParseeConfig *conf, char *id, char *banned) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/ban", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -95,11 +99,13 @@ ASKick(const ParseeConfig *conf, char *id, char *banned) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/kick", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -120,7 +126,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) { HttpClientContext *ctx = NULL; HashMap *json = NULL; - char *path, *ret; + char *path, *ret, *serv; + int status; if (!conf || !id) { return NULL; @@ -139,6 +146,11 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) { masquerade = HttpUrlEncode(masquerade); } + serv = strchr(id, ':'); + if (serv) + { + serv = serv + 1; + } id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/join/", id, "?", @@ -152,7 +164,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(path); json = HashMapCreate(); ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); + status = ParseeSetRequestJSON(ctx, json); JsonFree(json); json = JsonDecode(HttpClientStream(ctx)); @@ -163,6 +175,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(masquerade); Free(id); + (void) serv; // TODO + return ret; } void diff --git a/src/Main.c b/src/Main.c index f596126..10c809f 100644 --- a/src/Main.c +++ b/src/Main.c @@ -298,6 +298,7 @@ Main(Array *args, HashMap *env) "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" ); ASSetName(parsee_conf, parsee, "Parsee bridge"); + Free(parsee); } diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 2c5c5bd..32050dd 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -100,7 +100,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) char *jid = ParseeEncodeMXID(state_key); char *sha = NULL, *mime = NULL; char *avatar = ASGetAvatar(data->config, NULL, state_key); - char *url = ParseeToUnauth(data, avatar); + char *url = ParseeToUnauth(data, avatar, NULL); chat_id = ParseeGetFromRoomID(data, room_id); ASGetMIMESHA(data->config, avatar, &mime, &sha); @@ -417,7 +417,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) type = direct ? "chat" : "groupchat"; user = GrabString(json, 1, "xmpp_user"); - unauth = ParseeToUnauth(data, url); + unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename")); encoded_from = ParseeEncodeMXID(m_sender); xmppified_user = StrConcat(3, diff --git a/src/Parsee/User.c b/src/Parsee/User.c index 5a945ea..a9320d8 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -687,12 +687,13 @@ end: #include char * -ParseeToUnauth(ParseeData *data, char *mxc) +ParseeToUnauth(ParseeData *data, char *mxc, char *filename) { Uri *url = NULL; char *ret; char *key, *hmac; #define PAT "%s/media/%s%s?hmac=%s" +#define PATF "%s/media/%s%s/%s?hmac=%s" size_t l; if (!data || !mxc) { @@ -713,19 +714,43 @@ ParseeToUnauth(ParseeData *data, char *mxc) hmac = ParseeHMACS(data->id, key); Free(key); - l = snprintf(NULL, 0, - PAT, - data->config->media_base, - url->host, url->path, - hmac - ); + if (!filename) + { + l = snprintf(NULL, 0, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); + } + else + { + l = snprintf(NULL, 0, + PATF, + data->config->media_base, + url->host, url->path, filename, + hmac + ); + } ret = Malloc(l + 3); - snprintf(ret, l + 1, - PAT, - data->config->media_base, - url->host, url->path, - hmac - ); + if (!filename) + { + snprintf(ret, l + 1, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); + } + else + { + snprintf(ret, l + 1, + PATF, + data->config->media_base, + url->host, url->path, filename, + hmac + ); + } UriFree(url); Free(hmac); return ret; diff --git a/src/Unistr.c b/src/Unistr.c index a1eb1e4..7eab2e0 100644 --- a/src/Unistr.c +++ b/src/Unistr.c @@ -277,3 +277,38 @@ UnistrGetOffset(Unistr *str, uint32_t sep) } return 0; } +size_t +UnistrGetUTFOffset(char *cstr, size_t unicode) +{ + Unistr *tmp; + size_t ret = 0; + if (!cstr) + { + return 0; + } + + tmp = UnistrCreate(cstr); + for (size_t i = 0; i < unicode && i < tmp->length; i++) + { + uint32_t codepoint = tmp->codepoints[i]; + if (codepoint >= 0x0000 && codepoint <= 0x007F) + { + ret += 1; + } + else if (codepoint >= 0x0080 && codepoint <= 0x07FF) + { + ret += 2; + } + else if (codepoint >= 0x0800 && codepoint <= 0xFFFF) + { + ret += 3; + } + else if (codepoint >= 0x010000 && codepoint <= 0x10FFFF) + { + ret += 4; + } + } +end: + Free(tmp); + return ret; +} diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 27a658e..58c3876 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -159,6 +159,31 @@ XMPPGetReply(XMLElement *elem) return HashMapGet(rep->attrs, "id"); } +ssize_t +XMPPGetReplyOffset(XMLElement *elem) +{ + if (!elem) + { + return -1; + } + for (size_t i = 0; i < ArraySize(elem->children); i++) + { + XMLElement *child = ArrayGet(elem->children, i); + char *xmlns = HashMapGet(child->attrs, "xmlns"); + char *xfor = HashMapGet(child->attrs, "for"); + if (StrEquals(child->name, "fallback") && + StrEquals(xmlns, "urn:xmpp:feature-fallback:0") && + StrEquals(xfor, "urn:xmpp:reply:0")) + { + XMLElement *body = XMLookForUnique(child, "body"); + if (body && HashMapGet(body->attrs, "end")) + { + return strtol(HashMapGet(body->attrs, "end"), NULL, 10); + } + } + } + return -1; +} char * XMPPGetModeration(XMLElement *stanza) { diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 5497f19..c4066d9 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -324,8 +325,9 @@ end_error: { Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime)); } + char *trim = ParseeTrimJID(from); if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && - to && *to == '@') + to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL)) { DbRef *room_ref; HashMap *room_json; @@ -344,6 +346,7 @@ end_error: ParseePushDMRoom(args, to, from, room); } } + Free(trim); /* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER * SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED @@ -518,6 +521,15 @@ end_error: * too. Go figure. */ size_t off = reply_to ? ParseeFindDatastart(data->data) : 0; + size_t stanzaoff = XMPPGetReplyOffset(stanza); + if (reply_to && stanzaoff != -1) + { + off = UnistrGetUTFOffset(data->data, stanzaoff); + } + if (data->data && off >= strlen(data->data)) + { + off = 0; + } HashMap *ev = MatrixCreateMessage(data->data + off); if (reply_to) { diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 5726286..ea5d18b 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -368,7 +368,7 @@ extern char * ParseeDMEventFromID(ParseeData *d, char *r_id, char *ori_id); extern char * ParseeDMEventFromSID(ParseeData *d, char *r_id, char *ori_id); /* Gets a Parsee "shim" link to an MXC, usable as unauth for a limited time */ -extern char * ParseeToUnauth(ParseeData *data, char *mxc); +extern char * ParseeToUnauth(ParseeData *data, char *mxc, char *filename); extern void ParseeInitialiseOIDTable(void); extern void ParseePushOIDTable(char *muc, char *occupant); diff --git a/src/include/Routes.h b/src/include/Routes.h index b927eff..664032b 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -79,7 +79,8 @@ typedef struct ParseeCmdArg { X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \ X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \ X_ROUTE("/_matrix/app/v1/ping", RoutePing) \ - X_ROUTE("/media/(.*)/(.*)", RouteMedia) + X_ROUTE("/media/(.*)/(.*)", RouteMedia) \ + X_ROUTE("/media/(.*)/(.*)/(.*)", RouteMedia) #define X_ROUTE(path, name) extern void * name(Array *, void *); ROUTES diff --git a/src/include/Unistring.h b/src/include/Unistring.h index d90d61c..7eff7a1 100644 --- a/src/include/Unistring.h +++ b/src/include/Unistring.h @@ -82,4 +82,10 @@ extern Unistr * UnistrFilter(Unistr *str, UnistrFilterFunc filter); * -------- * Returns: an offset of the first line to not start by {c} | 0 */ extern size_t UnistrGetOffset(Unistr *str, uint32_t sep); + +/** Locates the new index within a regular UTF-8 sequence from an index + * within all codepoints. + * ----------------------------------- + * Returns: an offset */ +extern size_t UnistrGetUTFOffset(char *cstr, size_t unicode); #endif diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 22e98f6..3b53f04 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -90,6 +90,9 @@ extern char * XMPPGetRetractedID(XMLElement *); /* Get the replied-to stanza ID, if existent. */ extern char * XMPPGetReply(XMLElement *elem); +/* Get the replied-to stanza offset, if existent. */ +extern ssize_t XMPPGetReplyOffset(XMLElement *elem); + /** Get the moderated message ID(as a stanza ID/plain ID) from a moderation * stanza, that lives *alongside* the stanza itself. * ---------------------------------------------------------------------- From c96f0486ff48a2a0ea99cde3bff86fe17127470e Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 08:30:18 +0000 Subject: [PATCH 27/45] [FIX/WIP] Chrck URI before doing anything funny --- src/MatrixEventHandler.c | 4 ++++ src/Parsee/Utils/Formatting.c | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 32050dd..17a3bb9 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -358,6 +358,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) static void ParseeMessageHandler(ParseeData *data, HashMap *event) { + if (!data || !event) + { + return; + } XMPPComponent *jabber = data->jabber; StanzaBuilder *builder = NULL; DbRef *ref = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 44e255a..dc7b1a6 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -58,7 +58,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF } \ } \ while (0) - switch (elem->type) + switch (elem ? elem->type : -1) { case XML_ELEMENT_DATA: Concat(elem->data); @@ -155,7 +155,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF { char *href = HashMapGet(elem->attrs, "href"); Uri *pref = UriParse(href); - if (StrEquals(pref->host, "matrix.to")) + if (pref && StrEquals(pref->host, "matrix.to")) { /* TODO: Check if the element here is a Matrix.TO * pointing to a Parsee user. */ From cdbdc9345ad963fcbd9babe6a9d185efebc79441 Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 17:44:09 +0000 Subject: [PATCH 28/45] [MOD] Don't always add two newlines --- src/Parsee/Utils/Formatting.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index dc7b1a6..07cc191 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -149,7 +149,10 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF Concat(subxep); Free(subxep); } - Concat("\n"); + if (i != 0) + { + Concat("\n"); + } } else if (StrEquals(elem->name, "a")) { From 0a4aa45de5f76625ca9f97ff4303f08a3cbd126b Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 20:24:06 +0000 Subject: [PATCH 29/45] [FIX] Add proper includes --- src/XMPP/Stanza.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 58c3876..a2daf5a 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -7,6 +7,8 @@ #include #include +#include + void XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact) From fb44ad4bf63816c34678ee088b84829f6b8aeb05 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 18 Feb 2025 22:52:44 +0000 Subject: [PATCH 30/45] [FIX] Go back to using room IDs I already gave up on urandom anyways. --- src/Commands/Plumb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Plumb.c b/src/Commands/Plumb.c index bee94f2..f51e4e6 100644 --- a/src/Commands/Plumb.c +++ b/src/Commands/Plumb.c @@ -59,7 +59,7 @@ CommandHead(CmdPlumb, cmd, argp) goto end; } - chat_id = ParseePushMUC(args->data, room, muc); // ew. + chat_id = ParseePushMUC(args->data, room_id, muc); if (chat_id) { char *rev = StrConcat(2, muc, "/parsee"); From fd1b3499b684cb4be69bc0ce4474d74c1ef1ef17 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 15:32:52 +0000 Subject: [PATCH 31/45] [ADD] Allow the main component to be used to issue commands --- src/AS/Send.c | 7 ------- src/XMPPThread/Stanzas/IQ.c | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/AS/Send.c b/src/AS/Send.c index 9b81a71..6811548 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -69,13 +69,6 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u reply = JsonDecode(HttpClientStream(ctx)); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); - if (!ret) - { - Log(LOG_ERR, "Got %s from HTTP", HttpStatusToString(status)); - JsonEncode(reply, StreamStdout(), JSON_PRETTY); - StreamPrintf(StreamStdout(), "\n"); - StreamFlush(StreamStdout()); - } JsonFree(reply); HttpClientContextFree(ctx); diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 5dc9bc3..1b5087d 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -374,7 +374,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) bool IQIsCommandList(ParseeData *args, XMLElement *stanza) { - char *parsee = NULL; + char *parsee = NULL, *to; XMLElement *query = XMLookForTKV( stanza, "query", "xmlns", "http://jabber.org/protocol/disco#items" @@ -389,7 +389,8 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza) } parsee = ParseeJID(args); - ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee); + to = HashMapGet(stanza->attrs, "to"); + ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host); Free(parsee); return ret; From b78f7b6ab32f90a719d546c10f4ae2182bd21af2 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 15:38:22 +0000 Subject: [PATCH 32/45] [ADD/TOOL] Add -j flag for parsee-config --- etc/man/man1/parsee-config.1 | 10 +++++++++- tools/config.c | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/etc/man/man1/parsee-config.1 b/etc/man/man1/parsee-config.1 index a297075..5dc258b 100644 --- a/etc/man/man1/parsee-config.1 +++ b/etc/man/man1/parsee-config.1 @@ -1,6 +1,6 @@ ." The last field is the codename, by the way. ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee-config 1 "Parsee Utility" "star-of-hope" +.TH parsee-config 1 "Parsee Utility" "lunar-rainbow" .SH NAME parsee-config - generate a basic configuration file @@ -11,6 +11,7 @@ parsee-config .B [-s SHARED_SECRET] .B [-m MEDIA_URL] .B [-J JABBER_HOST] +.B [-j JABBER_ADDR] .B [-p JABBER_PORT] .B [-d DATABASE] .B [-M MAX_STANZA] @@ -34,6 +35,7 @@ $ parsee-config \\ -H 'blow.hole' \\ -s 'The Dark Shared Secret' \\ -J 'xmpp.blow.hole' \\ + -j 'localhost' \\ -S 128 .fi .if n \{\ @@ -68,6 +70,12 @@ media. It must be publicly accessible (behind a reverse proxy to HTTP:7642) .I JABBER_HOST is used as the component host for Parsee. .TP +.BR -j JABBER_ADDR +.I JABBER_ADDR +can optionally be used to change the hostname Parsee will try to contact +for XMPP. Users should ideally use localhost (or a hostname pointing to +the server itself), as XMPP component streams are not encrypted. +.TP .BR -p JABBER_PORT .I JABBER_PORT is used as the component post for Parsee. Parsee uses it alongside diff --git a/tools/config.c b/tools/config.c index b949c14..d8e0aea 100644 --- a/tools/config.c +++ b/tools/config.c @@ -20,6 +20,7 @@ Main(Array *args, HashMap *env) Uri *api_base; char *homeserver = NULL, *jcp = NULL, *jabber = NULL; char *data = NULL, *media = NULL, *listen = NULL; + char *component_as = NULL; int flag, code = EXIT_FAILURE; int port = 5347; size_t lmdb_size = 0; @@ -28,7 +29,7 @@ Main(Array *args, HashMap *env) listen = "localhost"; ArgParseStateInit(&state); - while ((flag = ArgParse(&state, args, "H:J:s:d:p:m:l:S:M:")) != -1) + while ((flag = ArgParse(&state, args, "H:J:j:s:d:p:m:l:S:M:")) != -1) { switch (flag) { @@ -45,6 +46,9 @@ Main(Array *args, HashMap *env) case 'J': jabber = state.optArg; break; + case 'j': + component_as = state.optArg; + break; case 'd': data = state.optArg; break; @@ -123,6 +127,7 @@ Main(Array *args, HashMap *env) JsonSet(json, JsonValueString(media), 1, "media_base"); JsonSet(json, JsonValueString(listen), 1, "listen_as"); + JsonSet(json, JsonValueString(component_as), 1, "component_addr"); JsonEncode(json, file, JSON_PRETTY); StreamFlush(file); From 40e324246510585ea6fab202fcaac8e2aa6f6650 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 16:14:30 +0000 Subject: [PATCH 33/45] [ADD] Try to renegociate an XMPP stream on failure --- src/XMPPThread/ReListener.c | 126 ++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 28d117b..44f22f8 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -244,66 +244,96 @@ ParseeXMPPThread(void *argp) } } - while (true) + while (!args->halted) { - char *id; - - stanza = XMLDecode(jabber->stream, false); - if (!stanza) + while (true) { - /* Try to check if an error is abound */ - if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + char *id; + + stanza = XMLDecode(jabber->stream, false); + if (!stanza) { - Log(LOG_DEBUG, "RECEIVED EOF."); + /* Try to check if an error is abound */ + if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "RECEIVED EOF."); + } + break; } - break; - } - if (args->verbosity >= PARSEE_VERBOSE_STANZA) - { - Stream *output = StreamStderr(); - StreamPrintf(output, "-------STANZA BEGIN-------" "\n"); - XMLEncode(output, stanza); - StreamPrintf(output, "\n--------STANZA END--------" "\n"); - StreamFlush(output); - } - - id = HashMapGet(stanza->attrs, "id"); - if (id) - { - XMPPAwait *await; - /* Lock out the table to see if we're awaiting. */ - pthread_mutex_lock(&await_lock); - if ((await = HashMapGet(await_table, id))) + if (args->verbosity >= PARSEE_VERBOSE_STANZA) { - pthread_mutex_lock(&await->cond_lock); - await->stanza = stanza; - pthread_cond_signal(&await->condition); - pthread_mutex_unlock(&await->cond_lock); + Stream *output = StreamStderr(); + StreamPrintf(output, "-------STANZA BEGIN-------" "\n"); + XMLEncode(output, stanza); + StreamPrintf(output, "\n--------STANZA END--------" "\n"); + StreamFlush(output); + } - HashMapDelete(await_table, id); + id = HashMapGet(stanza->attrs, "id"); + if (id) + { + XMPPAwait *await; + /* Lock out the table to see if we're awaiting. */ + pthread_mutex_lock(&await_lock); + if ((await = HashMapGet(await_table, id))) + { + pthread_mutex_lock(&await->cond_lock); + await->stanza = stanza; + pthread_cond_signal(&await->condition); + pthread_mutex_unlock(&await->cond_lock); + HashMapDelete(await_table, id); + + pthread_mutex_unlock(&await_lock); + continue; + } pthread_mutex_unlock(&await_lock); - continue; } - pthread_mutex_unlock(&await_lock); - } - /* Push it into the stanza FIFO. A dispatcher thread should then - * be able to freely grab a value(locked by a mutex). We can't make - * dispatchers read stanzas on their own, since that's _not_ how - * streams work, but this should mitigate some issues, and allow a - * few threads to be busy, while the rest of Parsee works. */ - PushStanza(&info, stanza); + /* Push it into the stanza FIFO. A dispatcher thread should then + * be able to freely grab a value(locked by a mutex). We can't make + * dispatchers read stanzas on their own, since that's _not_ how + * streams work, but this should mitigate some issues, and allow a + * few threads to be busy, while the rest of Parsee works. */ + PushStanza(&info, stanza); + } + pthread_mutex_lock(&args->halt_lock); + if (!args->halted) + { + Log(LOG_WARNING, "XMPP server is closing stream..."); + for (size_t i = 0; i < 50; i++) + { + UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */ + + Log(LOG_WARNING, "Restarting XMPP stream."); + /* This is the part where a new connection is being considered */ + XMPPFinishCompStream(jabber); + XMPPEndCompStream(jabber); + + args->jabber = XMPPInitialiseCompStream( + args->config->component_addr, + args->config->component_host, + args->config->component_port + ); + jabber = args->jabber; + if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret)) + { + /* Oops, there is something wrong! */ + Log(LOG_ERR, "Couldn't authenticate to XMPP server"); + XMPPEndCompStream(jabber); + args->jabber = NULL; + jabber = NULL; + if (i == 4) + { + pthread_mutex_unlock(&args->halt_lock); + break; + } + } + } + } + pthread_mutex_unlock(&args->halt_lock); } - pthread_mutex_lock(&args->halt_lock); - if (!args->halted) - { - Log(LOG_WARNING, "XMPP server is closing stream..."); - Log(LOG_WARNING, "Stopping %s...", NAME); - error = true; - } - pthread_mutex_unlock(&args->halt_lock); info.running = false; for (i = 0; i < info.available_dispatchers; i++) From 6762d1c1ce777ee8529afbc6e4d5aff2d994a8db Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 18:15:49 +0100 Subject: [PATCH 34/45] [FIX/WIP] remove .guix from gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5f7d56c..09b6fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ tags !.forgejo/* !.forgejo/** +!.guix +!.guix/* +!.guix/** + From a2c1de52dceb9ef39e1afacd0afafddd86141b1b Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 18:16:24 +0100 Subject: [PATCH 35/45] [ADD] new guix.scm with cytoplasm --- .guix/modules/parsee.scm | 43 ++++++++++++++++++++++++++++++++++++++++ guix.scm | 1 + 2 files changed, 44 insertions(+) create mode 100644 .guix/modules/parsee.scm create mode 120000 guix.scm diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm new file mode 100644 index 0000000..f2d6f48 --- /dev/null +++ b/.guix/modules/parsee.scm @@ -0,0 +1,43 @@ +(define-module (parsee) + #:use-module (guix packages) + #:use-module (guix git-download) + #:use-module (guix build-system gnu) + #:use-module (guix gexp) + #:use-module (guix utils) + #:use-module (gnu packages tls) + #:use-module ((guix licenses) #:prefix license:)) + +(define-public cytoplasm + (package + (name "cytoplasm") + (version "0.4.1") + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://git.telodendria.io/Telodendria/Cytoplasm") + (commit (string-append "v" version)))) + (sha256 + (base32 "13iwh37xfmz98vmb6dzl4v6vlxcis5q85c8rzmv3fs28khsfk45n")))) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:phases #~(modify-phases %standard-phases + (add-before 'configure 'add-ld-flags + (lambda _ + (substitute* "./configure" + (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) + (string-append flags " -Wl,-rpath=" #$output "/lib\""))) + (mkdir-p (string-append #$output "/lib")))) + (replace 'configure + (lambda* (#:key configure-flags #:allow-other-keys) + (apply invoke `("./configure" + ,(string-append "--prefix=" #$output) + ,@configure-flags))))))) + (inputs (list openssl)) + (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") + (synopsis "General-purpose high-level networked C library") + (description "Cytoplasm is a general-purpose C library for creating +high-level (particularly networked and multi-threaded) C applications.") + (license license:expat))) diff --git a/guix.scm b/guix.scm new file mode 120000 index 0000000..021acc5 --- /dev/null +++ b/guix.scm @@ -0,0 +1 @@ +.guix/modules/parsee.scm \ No newline at end of file From b50b9bd6155ffc03baa7c683e35702c4b84127e3 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:23:03 +0100 Subject: [PATCH 36/45] [FIX] pin cytoplasm to the latest commit --- .guix/modules/parsee.scm | 68 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm index f2d6f48..3c5ce83 100644 --- a/.guix/modules/parsee.scm +++ b/.guix/modules/parsee.scm @@ -8,36 +8,40 @@ #:use-module ((guix licenses) #:prefix license:)) (define-public cytoplasm - (package - (name "cytoplasm") - (version "0.4.1") - (source - (origin - (method git-fetch) - (uri (git-reference - (url "https://git.telodendria.io/Telodendria/Cytoplasm") - (commit (string-append "v" version)))) - (sha256 - (base32 "13iwh37xfmz98vmb6dzl4v6vlxcis5q85c8rzmv3fs28khsfk45n")))) - (build-system gnu-build-system) - (arguments - (list - #:tests? #f - #:phases #~(modify-phases %standard-phases - (add-before 'configure 'add-ld-flags - (lambda _ - (substitute* "./configure" - (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) - (string-append flags " -Wl,-rpath=" #$output "/lib\""))) - (mkdir-p (string-append #$output "/lib")))) - (replace 'configure - (lambda* (#:key configure-flags #:allow-other-keys) - (apply invoke `("./configure" - ,(string-append "--prefix=" #$output) - ,@configure-flags))))))) - (inputs (list openssl)) - (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") - (synopsis "General-purpose high-level networked C library") - (description "Cytoplasm is a general-purpose C library for creating + (let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f") + (revision "0")) + (package + (name "cytoplasm") + (version (git-version "0.4.1" revision commit)) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://git.telodendria.io/Telodendria/Cytoplasm") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 "09x5xfswryf3wjs1synh972yr2fmpjmffi7pjyjdzb4asqh4whrv")))) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:configure-flags #~'("--with-lmdb") + #:phases #~(modify-phases %standard-phases + (add-before 'configure 'add-ld-flags + (lambda _ + (substitute* "./configure" + (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) + (string-append flags " -Wl,-rpath=" #$output "/lib\""))) + (mkdir-p (string-append #$output "/lib")))) + (replace 'configure + (lambda* (#:key configure-flags #:allow-other-keys) + (apply invoke `("./configure" + ,(string-append "--prefix=" #$output) + ,@configure-flags))))))) + (inputs (list openssl lmdb)) + (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") + (synopsis "General-purpose high-level networked C library") + (description "Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications.") - (license license:expat))) + (license license:expat)))) From 44473878d09b806e40d0ad146b867610712dd8a9 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:23:39 +0100 Subject: [PATCH 37/45] [ADD] add parsee guix package --- .guix/modules/parsee.scm | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm index 3c5ce83..75b8b64 100644 --- a/.guix/modules/parsee.scm +++ b/.guix/modules/parsee.scm @@ -5,8 +5,14 @@ #:use-module (guix gexp) #:use-module (guix utils) #:use-module (gnu packages tls) + #:use-module (gnu packages databases) #:use-module ((guix licenses) #:prefix license:)) +(define vcs-file? + (or (git-predicate + (dirname (dirname (current-source-directory)))) + (const #t))) + (define-public cytoplasm (let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f") (revision "0")) @@ -45,3 +51,32 @@ (description "Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications.") (license license:expat)))) + +(define-public parsee + (package + (name "parsee") + (version "0.0.1-git") + (source + (local-file "../.." "parsee-checkout" + #:recursive? #t + #:select? vcs-file?)) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:make-flags #~(list "CC=gcc" + (string-append "CYTO_INC=" #$cytoplasm "/include") + (string-append "CYTO_LIB=" #$cytoplasm "/lib") + (string-append "PREFIX=" #$output)) + #:phases #~(modify-phases %standard-phases + (replace 'configure + (lambda* (#:key inputs #:allow-other-keys) + (let ((gcc (string-append (assoc-ref inputs "gcc") "/bin/gcc"))) + (invoke gcc "configure.c" "-o" "configure") + (invoke "./configure"))))))) + (home-page "https://git.kappach.at/lda/Parsee") + (synopsis "Jealous Matrix to XMPP bridge") + (description "Parsee is a Matrix-XMPP bridge written in C99 with Cytoplasm.") + (license license:agpl3+))) + +parsee From 680b4261c25f74ed1551ec75d9efc07346e6bf56 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:54:45 +0100 Subject: [PATCH 38/45] [ADD] small documentation paragraph --- README.MD | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.MD b/README.MD index ce51662..ee223de 100644 --- a/README.MD +++ b/README.MD @@ -43,6 +43,13 @@ Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendri Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but you can get away without those if you're adventurous). +### BUILDING WITH GUIX +If you have [Guix](https://guix.gnu.org/) installed, you can build Parsee using `guix package -f guix.scm`, or test it +using `guix shell -f guix.scm`. You can also generate a Docker/OCI image. +```sh +guix pack -f docker -S /bin=bin -L.guix/modules parsee +``` + ## RUNNING First off, you may want to configure Parsee by running the `config` tool(generally named `parsee-config` in most cases), with the correct flags, like here. From e21ebed134bd553dd7dd8e79f431467494b66fd6 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 23 Feb 2025 22:38:38 +0000 Subject: [PATCH 39/45] [META/ADD] Add Guix to the CHANGELOG quick und dirty --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04358d7..9b12530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ TBD - Parsee can now access the homeserver/component locally (rather than over the network) - The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' - Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. +- Add packaging for Guix (see #18) #### Bugfixes - Fix potential infinite loops when processing some messages. - Parsee now handles pinging puppets from Matrix more sanely. From 7454c8c597e93c4c19d98ee0946a555bb445f71e Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 9 Mar 2025 11:05:55 +0000 Subject: [PATCH 40/45] [FIX] Fix a few bugs about edits and XEP-393 formatting --- src/Parsee/Utils/Formatting.c | 12 ++++++++++-- src/XEP-0393.c | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 07cc191..ec95aef 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -247,6 +247,15 @@ GetHTMLBody(HashMap *event) } return GrabString(event, 2, "content", "formatted_body"); } +static char * +GetBodyFormat(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + return GrabString(event, 3, "content", "m.new_content", "format"); + } + return GrabString(event, 2, "content", "format"); +} char * ParseeXMPPify(ParseeData *data, HashMap *event) { @@ -267,8 +276,7 @@ ParseeXMPPify(ParseeData *data, HashMap *event) return NULL; } - format = JsonValueAsString(JsonGet(event, 2, "content", "format")); - if (!StrEquals(format, "org.matrix.custom.html")) + if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html")) { /* Settle for the raw body instead. */ char *body = GetRawBody(event); diff --git a/src/XEP-0393.c b/src/XEP-0393.c index a0f6be9..784bacf 100644 --- a/src/XEP-0393.c +++ b/src/XEP-0393.c @@ -329,8 +329,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent) break; case XEP393_MONO: head = XMLCreateTag("code"); + XMLAddChild(xmlparent, XMLCreateText("`")); XMLAddChild(xmlparent, head); - XMLAddChild(head, XMLCreateText("`")); break; case XEP393_SRKE: head = XMLCreateTag("s"); @@ -372,7 +372,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent) XMLAddChild(head, XMLCreateText("_")); break; case XEP393_MONO: - XMLAddChild(head, XMLCreateText("`")); + XMLAddChild(xmlparent, XMLCreateText("`")); break; case XEP393_SRKE: XMLAddChild(head, XMLCreateText("~")); From 45250096ad60ecc731bc70ada02106dd2e704749 Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:23:51 +0000 Subject: [PATCH 41/45] [FIX] argh --- src/AS/Media.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AS/Media.c b/src/AS/Media.c index 65f049c..5fbc659 100644 --- a/src/AS/Media.c +++ b/src/AS/Media.c @@ -150,7 +150,7 @@ ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha) return false; } - path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); cctx = ParseeCreateRequest(c, HTTP_GET, path); ASAuthenticateRequest(c, cctx); HttpRequestSendHeaders(cctx); @@ -195,7 +195,7 @@ ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len) return false; } - path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); cctx = ParseeCreateRequest(c, HTTP_GET, path); ASAuthenticateRequest(c, cctx); HttpRequestSendHeaders(cctx); From 7b8ed08e881da531f68f425db8f50b0c1943e5ac Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:39:15 +0000 Subject: [PATCH 42/45] [ADD] Optionally allow instance admins to ignore m.notice events --- CHANGELOG.md | 2 ++ README.MD | 2 +- src/MatrixEventHandler.c | 8 ++++++++ src/Parsee/Config.c | 2 ++ src/include/Parsee.h | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b12530..f4ffcde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ TBD - The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' - Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. - Add packaging for Guix (see #18) +- Parsee is now dependent on authenticated media to function. + (Please, Matrix, do _not_ touch anything, I do _not_ want to mess with this anymore) #### Bugfixes - Fix potential infinite loops when processing some messages. - Parsee now handles pinging puppets from Matrix more sanely. diff --git a/README.MD b/README.MD index ee223de..f9522e8 100644 --- a/README.MD +++ b/README.MD @@ -22,7 +22,7 @@ Please scream at me if that fails(or just doesn't run on a overclocked Raspberry ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" -These two having the same answer should be enough information. Also can I *just* have fun? +These two having the same answer should be enough information. One could also argue that both sides need to migrate(onboard) the other side, so a bridge may be a good way to start. diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 17a3bb9..638b5a3 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -367,6 +367,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) DbRef *ref = NULL; HashMap *json = NULL; + char *msgtype = GrabString(event, 2, "content", "msgtype"); char *m_sender = GrabString(event, 1, "sender"); char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); @@ -391,6 +392,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) if (new_content) body = new_content; } + if (data->config->ignore_bots && StrEquals(msgtype, "m.notice")) + { + Free(reply_id); + Free(xepd); + Free(unedited_id); + return; + } if (ParseeIsPuppet(data->config, m_sender) || ParseeManageBan(data, m_sender, id)) { diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 45e6fe5..b95e5e1 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -85,6 +85,8 @@ ParseeConfigLoad(char *conf) config->max_stanza_size = 10000; } + CopyToBool(ignore_bots, "ignore_bots"); + CopyToStr(media_base, "media_base"); CopyToStr(db_path, "db"); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index ea5d18b..4b1f7aa 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -44,6 +44,7 @@ typedef struct ParseeConfig { int homeserver_tls; bool accept_pings; + bool ignore_bots; /* ------- JABBER -------- */ From e3749817d3585aa0218bb9602f222ecac85f526f Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:49:40 +0000 Subject: [PATCH 43/45] [MOD/FIX] Be even more strict with Unicode filtering Now, only ASCII characters get to fit. --- src/MatrixEventHandler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 638b5a3..6311fac 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -21,7 +21,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char char *sender = GrabString(event, 1, "sender"); Unistr *uninick = UnistrCreate(name); - Unistr *filtered = UnistrFilter(uninick, UnistrIsBMP); + Unistr *filtered = UnistrFilter(uninick, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */ char *nick = UnistrC(filtered); char *rev = StrConcat(3, muc, "/", nick); int nonce = 0; From e617b5e8d47dfd8955c5e3565394e9f2b14a1204 Mon Sep 17 00:00:00 2001 From: LDA Date: Mon, 28 Apr 2025 02:18:57 +0200 Subject: [PATCH 44/45] [FIX/WIP] Unique redactions for moderation stanzas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, I'll consider Parsee dev to be dead until July 2026. J'ai le bac et déja un projet à faire, et si je peux même pas me concentrer sur mon futur en ne comptant pas Parsee. Feel free to make a good XMPP bridge, I don't really even want to touch either Matrix or XMPP anymore, you know? --- src/XMPPThread/Stanzas/Message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index c4066d9..13021df 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -277,7 +277,7 @@ end_error: Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime)); } - if (moderated) + if (moderated && !(!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))) { /* TODO: Parsee MUST check if it is a valid MUC */ char *resource = ParseeGetResource(from); From e331d110c7fa57e1a3fd96f8ff993cf59d714f9c Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 22 Jun 2025 14:00:12 +0200 Subject: [PATCH 45/45] [FIX] URL encode filenames --- src/Parsee/User.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Parsee/User.c b/src/Parsee/User.c index a9320d8..6e673bb 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -725,12 +726,14 @@ ParseeToUnauth(ParseeData *data, char *mxc, char *filename) } else { + char *encoded = HttpUrlEncode(filename); l = snprintf(NULL, 0, PATF, data->config->media_base, - url->host, url->path, filename, + url->host, url->path, encoded, hmac ); + Free(encoded); } ret = Malloc(l + 3); if (!filename)