From ca87972b3a65b28f117fa4f6e1c31104daae20cd Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 16 Nov 2024 14:11:32 +0100 Subject: [PATCH] [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", {}) \