Compare commits

..

5 commits

Author SHA1 Message Date
LDA
de40106932 Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-21 11:25:08 +01:00
LDA
81edbec1d0 Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-20 22:09:03 +01:00
LDA
e3979e0e6e Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-17 16:25:30 +01:00
LDA
6d7d85662b Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-16 14:36:09 +01:00
LDA
ca87972b3a [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!
2024-11-16 14:11:32 +01:00
89 changed files with 3889 additions and 1612 deletions

16
.gitignore vendored
View file

@ -4,11 +4,10 @@ parsee*
parsee parsee
*.swp *.swp
.* .*
data* data
data*/* data/*
Makefile Makefile
configure configure
gmon.out
tools/out tools/out
tools/out/* tools/out/*
@ -26,7 +25,10 @@ tags
!.forgejo/* !.forgejo/*
!.forgejo/** !.forgejo/**
!.guix # Janet
!.guix/* janet/*
!.guix/** janet/**
janet
extensions
extensions/*
extensions/**

View file

@ -1,82 +0,0 @@
(define-module (parsee)
#:use-module (guix packages)
#:use-module (guix git-download)
#:use-module (guix build-system gnu)
#:use-module (guix gexp)
#:use-module (guix utils)
#:use-module (gnu packages tls)
#:use-module (gnu packages databases)
#:use-module ((guix licenses) #:prefix license:))
(define vcs-file?
(or (git-predicate
(dirname (dirname (current-source-directory))))
(const #t)))
(define-public cytoplasm
(let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f")
(revision "0"))
(package
(name "cytoplasm")
(version (git-version "0.4.1" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.telodendria.io/Telodendria/Cytoplasm")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "09x5xfswryf3wjs1synh972yr2fmpjmffi7pjyjdzb4asqh4whrv"))))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:configure-flags #~'("--with-lmdb")
#:phases #~(modify-phases %standard-phases
(add-before 'configure 'add-ld-flags
(lambda _
(substitute* "./configure"
(("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags)
(string-append flags " -Wl,-rpath=" #$output "/lib\"")))
(mkdir-p (string-append #$output "/lib"))))
(replace 'configure
(lambda* (#:key configure-flags #:allow-other-keys)
(apply invoke `("./configure"
,(string-append "--prefix=" #$output)
,@configure-flags)))))))
(inputs (list openssl lmdb))
(home-page "https://git.telodendria.io/Telodendria/Cytoplasm")
(synopsis "General-purpose high-level networked C library")
(description "Cytoplasm is a general-purpose C library for creating
high-level (particularly networked and multi-threaded) C applications.")
(license license:expat))))
(define-public parsee
(package
(name "parsee")
(version "0.0.1-git")
(source
(local-file "../.." "parsee-checkout"
#:recursive? #t
#:select? vcs-file?))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:make-flags #~(list "CC=gcc"
(string-append "CYTO_INC=" #$cytoplasm "/include")
(string-append "CYTO_LIB=" #$cytoplasm "/lib")
(string-append "PREFIX=" #$output))
#:phases #~(modify-phases %standard-phases
(replace 'configure
(lambda* (#:key inputs #:allow-other-keys)
(let ((gcc (string-append (assoc-ref inputs "gcc") "/bin/gcc")))
(invoke gcc "configure.c" "-o" "configure")
(invoke "./configure")))))))
(home-page "https://git.kappach.at/lda/Parsee")
(synopsis "Jealous Matrix to XMPP bridge")
(description "Parsee is a Matrix-XMPP bridge written in C99 with Cytoplasm.")
(license license:agpl3+)))
parsee

View file

@ -13,22 +13,6 @@ commit done between releases.
*There is currently no beta releases of Parsee* *There is currently no beta releases of Parsee*
## Alpha ## Alpha
### v0.3.0[lunar-rainbow] <XX/XX/2025>
This is the first release of 2025!
TBD
#### New things
- Allow admins to remove users from the nofly list.
- Parsee can now access the homeserver/component locally (rather than over the network)
- The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...'
- Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running.
- Add packaging for Guix (see #18)
- Parsee is now dependent on authenticated media to function.
(Please, Matrix, do _not_ touch anything, I do _not_ want to mess with this anymore)
#### Bugfixes
- Fix potential infinite loops when processing some messages.
- Parsee now handles pinging puppets from Matrix more sanely.
### v0.2.0[star-of-hope] <8/11/2024> ### v0.2.0[star-of-hope] <8/11/2024>
Fixes some media metadata things, replaces the build system, Fixes some media metadata things, replaces the build system,
tries out avatar support some more, MUC contexts, and speeds tries out avatar support some more, MUC contexts, and speeds

View file

@ -18,11 +18,12 @@ A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solutio
and maybe as a testing ground for Cytoplasm features I sometimes add. and maybe as a testing ground for Cytoplasm features I sometimes add.
(Well, I'm *trying* to do that, at least. (Well, I'm *trying* to do that, at least.
Please scream at me if that fails(or just doesn't run on a overclocked Raspberry Pi 4B)) Please scream at me if that fails(or just doesn't run on a overclocked Raspberry
Pi 4B, which, by the way, is literally where Parsee+XMPP is running for now.))
### "Why not just use Matrix lol" ### "Why not just use Matrix lol"
### "Why not just use XMPP lol" ### "Why not just use XMPP lol"
These two having the same answer should be enough information. These two having the same answer should be enough information. Also can I *just* have fun?
One could also argue that both sides need to migrate(onboard) the other side, so One could also argue that both sides need to migrate(onboard) the other side, so
a bridge may be a good way to start. a bridge may be a good way to start.
@ -43,13 +44,6 @@ Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendri
Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but
you can get away without those if you're adventurous). you can get away without those if you're adventurous).
### BUILDING WITH GUIX
If you have [Guix](https://guix.gnu.org/) installed, you can build Parsee using `guix package -f guix.scm`, or test it
using `guix shell -f guix.scm`. You can also generate a Docker/OCI image.
```sh
guix pack -f docker -S /bin=bin -L.guix/modules parsee
```
## RUNNING ## RUNNING
First off, you may want to configure Parsee by running the `config` tool(generally named First off, you may want to configure Parsee by running the `config` tool(generally named
`parsee-config` in most cases), with the correct flags, like here. `parsee-config` in most cases), with the correct flags, like here.

View file

@ -1,6 +1,6 @@
CODE=lunar-rainbow CODE=star-of-hope
NAME=Parsee NAME=Parsee
VERSION=0.3.0 VERSION=0.2.0
BINARY=parsee BINARY=parsee
SOURCE=src SOURCE=src
INCLUDES=src/include INCLUDES=src/include

View file

@ -90,6 +90,22 @@ string_cat(char *in1, char *in2)
return out; return out;
} }
static char *
trim_nl(char *in)
{
char *tc;
if (!in)
{
return NULL;
}
while ((tc = strrchr(in, '\n')))
{
*tc = '\0';
}
return in;
}
typedef struct str_array { typedef struct str_array {
char **values; char **values;
@ -99,10 +115,6 @@ static str_array_t *
str_array_create(void) str_array_create(void)
{ {
str_array_t *ret = malloc(sizeof(*ret)); str_array_t *ret = malloc(sizeof(*ret));
if (!ret)
{
return NULL;
}
ret->values = NULL; ret->values = NULL;
ret->quantity = 0; ret->quantity = 0;
@ -204,6 +216,36 @@ failure:
free(line); free(line);
return NULL; return NULL;
} }
static int
exec_code(char *program, char *argv[])
{
pid_t forkRet;
if (!program || !argv)
{
return -1;
}
forkRet = fork();
if (forkRet == 0)
{
/* Child */
execvp(program, argv);
exit(0);
}
else
{
/* Parent */
int status;
if (waitpid(forkRet, &status, 0) == -1)
{
return -1;
}
return status;
}
/* We're not meant to ever be there, but TCC is stupid. */
return -1;
}
static char * static char *
strchrn(char *s, char c) strchrn(char *s, char c)
@ -211,6 +253,17 @@ strchrn(char *s, char c)
s = strchr(s, c); s = strchr(s, c);
return s ? s+1 : NULL; return s ? s+1 : NULL;
} }
static char *
strchrl(char *s, char c)
{
char *s1 = NULL;
while ((s = strchr(s, c)))
{
s1 = s;
s++;
}
return s1;
}
static str_array_t * static str_array_t *
split(char *dir) split(char *dir)
{ {
@ -315,7 +368,7 @@ write_objects(FILE *makefile, str_array_t *sources)
{ {
char *src = str_array_get(sources, i); char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o"); 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); fprintf(makefile, " %s", obj);
free(ofl); free(ofl);
@ -459,10 +512,12 @@ main_build(int argc, char *argv[])
{ {
FILE *makefile; FILE *makefile;
char *repo = cmd_stdout("git remote get-url origin"); char *repo = cmd_stdout("git remote get-url origin");
size_t i; size_t size, i;
ssize_t nread;
bool with_static = false, with_lmdb = false; bool with_static = false, with_lmdb = false;
int opt; int opt;
str_array_t *sources, *images, *utils, *aya; str_array_t *sources, *janet = NULL, *images, *utils, *aya;
char *janet_dir = NULL;
if (repo) if (repo)
{ {
@ -477,7 +532,7 @@ main_build(int argc, char *argv[])
repo = strdup("N/A"); repo = strdup("N/A");
} }
while ((opt = getopt(argc, argv, "sl")) != -1) while ((opt = getopt(argc, argv, "slJ:")) != -1)
{ {
switch (opt) switch (opt)
{ {
@ -488,17 +543,13 @@ main_build(int argc, char *argv[])
with_lmdb = with_static; with_lmdb = with_static;
with_static = true; with_static = true;
break; break;
case 'J':
janet_dir = strdup(optarg);
break;
} }
} }
makefile = fopen("Makefile", "w"); makefile = fopen("Makefile", "w");
if (!makefile)
{
fprintf(stderr, "Couldn't create Makefile.\n");
fprintf(stderr, "This isn't good, actually.\n");
return EXIT_FAILURE;
}
fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n"); fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n");
fprintf(makefile, "# Ideally do not touch, unless you have a very "); fprintf(makefile, "# Ideally do not touch, unless you have a very ");
fprintf(makefile, "good reason to do it. \n\n"); fprintf(makefile, "good reason to do it. \n\n");
@ -513,13 +564,22 @@ main_build(int argc, char *argv[])
/* Create all objects */ /* Create all objects */
sources = collect_sources("src", true, ".c"); 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"); images = collect_sources("etc/media", true, ".png");
fprintf(makefile, "binary:"); fprintf(makefile, "binary:");
write_objects(makefile, sources); write_objects(makefile, sources);
write_images(makefile, images); write_images(makefile, images);
fprintf(makefile, "\n\t"); fprintf(makefile, "\n\t");
{ {
fprintf(makefile, "$(CC) -o $(BINARY)"); fprintf(makefile, "$(CC) -lm -o $(BINARY)");
if (with_static) if (with_static)
{ {
fprintf(makefile, " -static"); fprintf(makefile, " -static");
@ -532,7 +592,7 @@ main_build(int argc, char *argv[])
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb"); if (with_lmdb) fprintf(makefile, " -llmdb");
} }
fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n"); fprintf(makefile, " -lCytoplasm -lm $(LDFLAGS)\n");
} }
/* Write rules for every source */ /* Write rules for every source */
@ -540,7 +600,7 @@ main_build(int argc, char *argv[])
{ {
char *src = str_array_get(sources, i); char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o"); 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", obj, src); fprintf(makefile, "%s: %s", obj, src);
analyse_dependencies(makefile, src); analyse_dependencies(makefile, src);
@ -558,6 +618,10 @@ main_build(int argc, char *argv[])
str_array_free(s); str_array_free(s);
fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) "); 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, "-DVERSION=\"\\\"$(VERSION)\\\"\" ");
fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" "); fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" ");
fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" "); fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" ");
@ -628,7 +692,7 @@ main_build(int argc, char *argv[])
fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb"); if (with_lmdb) fprintf(makefile, " -llmdb");
} }
fprintf(makefile, " -lCytoplasm $(CFLAGS)\n"); fprintf(makefile, " -lCytoplasm -lm $(CFLAGS)\n");
} }
free(ofl); free(ofl);
@ -716,6 +780,7 @@ main_build(int argc, char *argv[])
str_array_free(aya); str_array_free(aya);
fflush(makefile); fflush(makefile);
fclose(makefile); fclose(makefile);
free(janet_dir);
free(repo); free(repo);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-config 1 "Parsee Utility" "lunar-rainbow" .TH parsee-config 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee-config - generate a basic configuration file parsee-config - generate a basic configuration file
@ -11,7 +11,6 @@ parsee-config
.B [-s SHARED_SECRET] .B [-s SHARED_SECRET]
.B [-m MEDIA_URL] .B [-m MEDIA_URL]
.B [-J JABBER_HOST] .B [-J JABBER_HOST]
.B [-j JABBER_ADDR]
.B [-p JABBER_PORT] .B [-p JABBER_PORT]
.B [-d DATABASE] .B [-d DATABASE]
.B [-M MAX_STANZA] .B [-M MAX_STANZA]
@ -35,7 +34,6 @@ $ parsee-config \\
-H 'blow.hole' \\ -H 'blow.hole' \\
-s 'The Dark Shared Secret' \\ -s 'The Dark Shared Secret' \\
-J 'xmpp.blow.hole' \\ -J 'xmpp.blow.hole' \\
-j 'localhost' \\
-S 128 -S 128
.fi .fi
.if n \{\ .if n \{\
@ -70,12 +68,6 @@ media. It must be publicly accessible (behind a reverse proxy to HTTP:7642)
.I JABBER_HOST .I JABBER_HOST
is used as the component host for Parsee. is used as the component host for Parsee.
.TP .TP
.BR -j JABBER_ADDR
.I JABBER_ADDR
can optionally be used to change the hostname Parsee will try to contact
for XMPP. Users should ideally use localhost (or a hostname pointing to
the server itself), as XMPP component streams are not encrypted.
.TP
.BR -p JABBER_PORT .BR -p JABBER_PORT
.I JABBER_PORT .I JABBER_PORT
is used as the component post for Parsee. Parsee uses it alongside is used as the component post for Parsee. Parsee uses it alongside

View file

@ -1,70 +0,0 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-bridge-guidebook 7 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-bridge-guidebook - A short guidebook on running a Parsee bridge
.SH INTRODUCTION
.P
This manpage is intended to be a guidebook for Parsee administrators. It
is meant to show how to create an instance with an XMPP-Matrix server
(though it cannot be specific, due to their ecosystem diversity), how to
plumb rooms, and moderate them through.
.P
It also assumes Parsee is properly built and installed, in which case you
are seeing this from
.I man
itself.
.SH CONVENTIONS
This page shall assume a few things that
.B must
be changed to fit your configuration. Please read those carefully, or it
will come and bite at you!
.P
First off, it assumes that you have a domain at
.I blow.hole
and that any other domains related to Parsee are it's subdomains, as
you'll see.
.P
We also assume you're planning on making the XMPP component available at
.I j.blow.hole ,
and that Parsee can reach it by using
.I localhost:1234 .
It is highly recommended that Parsee can reach the component locally, as
the stream cannot be encrypted!
.P
The Parsee HTTP server (which is used for media and the appservice) shall
be reached through
.I https://p.blow.hole/
via a reverse proxy. This manual shall only show you what port to make
available, as there are many reverse proxy options available.
.P
Finally, the Matrix server will be publicly known as
.I m.blow.hole
.
That is, if
.B bob
is in it, then they shall be known as
.I @bob:m.blow.hole .
.SH SETTING UP
Setting up Parsee mainly involves creating a valid configuration file
and the database. Most of this however is dealt with by
.I parsee-config(1)
TODO
.P
.SH LICENSE
This document is under public domain, or CC0 if not allowed by local law.
.SH SEE ALSO
.B parsee(1), parsee-cmd-syntax(7)

View file

@ -1 +0,0 @@
.guix/modules/parsee.scm

View file

@ -150,7 +150,7 @@ ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha)
return false; return false;
} }
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path); cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx); ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx); HttpRequestSendHeaders(cctx);
@ -195,7 +195,7 @@ ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len)
return false; return false;
} }
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path); cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx); ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx); HttpRequestSendHeaders(cctx);

View file

@ -25,13 +25,11 @@ ASInvite(const ParseeConfig *conf, char *id, char *invited)
"@", conf->sender_localpart, "@", conf->sender_localpart,
":", conf->server_base ":", conf->server_base
); );
id = HttpUrlEncode(id);
path = StrConcat(5, path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/invite", "/_matrix/client/v3/rooms/", id, "/invite",
"?user_id=", bridge "?user_id=", bridge
); );
Free(bridge); Free(bridge);
Free(id);
ctx = ParseeCreateRequest( ctx = ParseeCreateRequest(
conf, conf,
@ -62,13 +60,11 @@ ASBan(const ParseeConfig *conf, char *id, char *banned)
"@", conf->sender_localpart, "@", conf->sender_localpart,
":", conf->server_base ":", conf->server_base
); );
id = HttpUrlEncode(id);
path = StrConcat(5, path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/ban", "/_matrix/client/v3/rooms/", id, "/ban",
"?user_id=", bridge "?user_id=", bridge
); );
Free(bridge); Free(bridge);
Free(id);
ctx = ParseeCreateRequest( ctx = ParseeCreateRequest(
conf, conf,
@ -99,13 +95,11 @@ ASKick(const ParseeConfig *conf, char *id, char *banned)
"@", conf->sender_localpart, "@", conf->sender_localpart,
":", conf->server_base ":", conf->server_base
); );
id = HttpUrlEncode(id);
path = StrConcat(5, path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/kick", "/_matrix/client/v3/rooms/", id, "/kick",
"?user_id=", bridge "?user_id=", bridge
); );
Free(bridge); Free(bridge);
Free(id);
ctx = ParseeCreateRequest( ctx = ParseeCreateRequest(
conf, conf,
@ -126,8 +120,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
HashMap *json = NULL; HashMap *json = NULL;
char *path, *ret, *serv; char *path, *ret;
int status;
if (!conf || !id) if (!conf || !id)
{ {
return NULL; return NULL;
@ -146,11 +139,6 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{ {
masquerade = HttpUrlEncode(masquerade); masquerade = HttpUrlEncode(masquerade);
} }
serv = strchr(id, ':');
if (serv)
{
serv = serv + 1;
}
id = HttpUrlEncode(id); id = HttpUrlEncode(id);
path = StrConcat(5, path = StrConcat(5,
"/_matrix/client/v3/join/", id, "?", "/_matrix/client/v3/join/", id, "?",
@ -164,7 +152,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
Free(path); Free(path);
json = HashMapCreate(); json = HashMapCreate();
ASAuthenticateRequest(conf, ctx); ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, json); ParseeSetRequestJSON(ctx, json);
JsonFree(json); JsonFree(json);
json = JsonDecode(HttpClientStream(ctx)); json = JsonDecode(HttpClientStream(ctx));
@ -175,8 +163,6 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
Free(masquerade); Free(masquerade);
Free(id); Free(id);
(void) serv; // TODO
return ret; return ret;
} }
void void

View file

@ -33,11 +33,6 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u
char *path; char *path;
char *txn, *ret; char *txn, *ret;
char *ts_str; char *ts_str;
HttpStatus status;
if (!ret)
{
Log(LOG_ERR, "%", ret);
}
HashMap *reply; HashMap *reply;
if (!conf || !id || !type || !user || !c) if (!conf || !id || !type || !user || !c)
{ {
@ -52,20 +47,18 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u
ts_str = TSToStr(ts); ts_str = TSToStr(ts);
txn = StrRandom(16); txn = StrRandom(16);
id = HttpUrlEncode(id);
path = StrConcat(11, path = StrConcat(11,
"/_matrix/client/v3/rooms/", "/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?", id, "/send/", type, "/", txn, "?",
"user_id=", user, "&ts=", ts_str "user_id=", user, "&ts=", ts_str
); );
Free(id);
Free(txn); Free(txn);
Free(ts_str); Free(ts_str);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path); ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path); Free(path);
ASAuthenticateRequest(conf, ctx); ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, c); ParseeSetRequestJSON(ctx, c);
reply = JsonDecode(HttpClientStream(ctx)); reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id")));

View file

@ -28,25 +28,6 @@ CommandHead(CmdBanUser, cmd, argp)
BotDestroy(); BotDestroy();
} }
CommandHead(CmdNoFlyListDel, cmd, argp)
{
ParseeCmdArg *args = argp;
ParseeData *data = args->data;
HashMap *event = args->event;
char *user = HashMapGet(cmd->arguments, "user");
BotInitialise();
if (!user)
{
BotDestroy();
return;
}
ReplySprintf("Unbanning %s", user);
ParseeGlobalUnban(data, user);
BotDestroy();
}
CommandHead(CmdNoFlyList, cmd, argp) CommandHead(CmdNoFlyList, cmd, argp)
{ {
ParseeCmdArg *args = argp; ParseeCmdArg *args = argp;

View file

@ -12,7 +12,7 @@ CommandHead(CmdSetPL, cmd, argp)
char *user = HashMapGet(cmd->arguments, "user"); char *user = HashMapGet(cmd->arguments, "user");
char *room = HashMapGet(cmd->arguments, "room"); char *room = HashMapGet(cmd->arguments, "room");
char *pl_str = HashMapGet(cmd->arguments, "pl"); 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; HashMap *map;

74
src/Extensions.c Normal file
View file

@ -0,0 +1,74 @@
#ifndef JANET
#include <Extension.h>
#include <stdlib.h>
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

366
src/Extensions/DB.c Normal file
View file

@ -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

222
src/Extensions/Data.c Normal file
View file

@ -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

207
src/Extensions/Functions.c Normal file
View file

@ -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

View file

@ -0,0 +1,34 @@
#ifdef JANET
#include <janet.h>
#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

258
src/Extensions/Http.c Normal file
View file

@ -0,0 +1,258 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include "Extensions/Functions.h"
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/HttpClient.h>
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

98
src/Extensions/Internal.h Normal file
View file

@ -0,0 +1,98 @@
#ifndef PARSEE_IEXTENSIONS_H
#define PARSEE_IEXTENSIONS_H
#ifdef JANET
#include <Extension.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XML.h>
#include <janet.h>
#include <pthread.h>
#include <stdlib.h>
#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

170
src/Extensions/JSON.c Normal file
View file

@ -0,0 +1,170 @@
#ifdef JANET
#include <janet.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <StringStream.h>
#include <XML.h>
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

30
src/Extensions/Jabber.c Normal file
View file

@ -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

304
src/Extensions/Janet.c Normal file
View file

@ -0,0 +1,304 @@
#ifdef JANET
#include <Extension.h>
#include <stdlib.h>
#include <janet.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Parsee.h>
#include <XML.h>
#include <pthread.h>
#include <dirent.h>
#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

86
src/Extensions/KV.c Normal file
View file

@ -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

149
src/Extensions/Matrix.c Normal file
View file

@ -0,0 +1,149 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include <AS.h>
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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,580 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include <XMPPCommand.h>
#include <XMPPFormTool.h>
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

View file

@ -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

357
src/Extensions/XML.c Normal file
View file

@ -0,0 +1,357 @@
#ifdef JANET
#include <janet.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <StringStream.h>
#include <XML.h>
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

View file

@ -22,6 +22,9 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
/* Basic headers */ /* Basic headers */
HttpResponseStatus(ctx, HTTP_OK); HttpResponseStatus(ctx, HTTP_OK);
HttpResponseHeader(ctx, "Server", NAME "/v" VERSION); HttpResponseHeader(ctx, "Server", NAME "/v" VERSION);
#ifdef PLATFORM_WINDOWS
HttpResponseHeader(ctx, "X-Powered-By", "some weirdows");
#endif
HttpResponseHeader(ctx, "Connection", "close"); HttpResponseHeader(ctx, "Connection", "close");
arg.data = data; arg.data = data;
@ -73,7 +76,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path
ctx = HttpRequest( ctx = HttpRequest(
meth, meth,
conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, HTTP_FLAG_TLS,
conf->homeserver_port, conf->homeserver_host, conf->homeserver_port, conf->homeserver_host,
path path
); );

View file

@ -13,11 +13,16 @@
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <StanzaBuilder.h> #include <Unistring.h>
#include <Parsee.h> #include <Parsee.h>
#include <XMPP.h> #include <XMPP.h>
#include <AS.h> #include <AS.h>
#ifdef JANET
#include <janet.h>
#endif
#include <Extension.h>
static HttpServer *server = NULL; static HttpServer *server = NULL;
static pthread_t xmpp_thr; static pthread_t xmpp_thr;
static XMPPComponent *jabber = NULL; static XMPPComponent *jabber = NULL;
@ -63,39 +68,23 @@ ParseeCheckMatrix(void *datp)
{ {
static volatile uint64_t streak = 0; static volatile uint64_t streak = 0;
ParseeData *data = datp; ParseeData *data = datp;
if (data->config->accept_pings && !ASPing(data->config)) if (!ASPing(data->config))
{ {
Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host);
if (++streak == 10) if (++streak >= 5)
{ {
DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); Log(LOG_ERR, "This has been at least the fifth time in a row.");
HashMap *json = DbJson(ref); Log(LOG_ERR, "Please check if your homeserver is active.");
HashMap *mucs = GrabObject(json, 1, "mucs"); Log(LOG_ERR, "%s shall now exit...", NAME);
char *muc;
void *ignored;
pthread_mutex_lock(&data->halt_lock);
data->halted = true;
pthread_mutex_unlock(&data->halt_lock);
/* Notify any potential MUCs about this */ XMPPFinishCompStream(data->jabber);
while (HashMapIterate(mucs, &muc, &ignored)) pthread_join(xmpp_thr, NULL);
{ Log(LOG_INFO, "Stopping server...");
char *id = StrRandom(32); HttpServerStop(data->server);
char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee");
StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id);
SetStanzaType(b, "groupchat");
SetStanzaBody(b,
"This bridge hasn't been able to reach the Matrix host, and "
"as such, some messages may not have been sent over."
);
WriteoutStanza(b, data->jabber, 0);
DestroyStanzaBuilder(b);
Free(sender);
Free(id);
}
(void) ignored;
DbUnlock(data->db, ref);
} }
return; return;
} }
@ -115,6 +104,8 @@ Main(Array *args, HashMap *env)
int http = 8; int http = 8;
int verbose = 0; int verbose = 0;
Extensions *ext_ctx = NULL;
start = UtilTsMillis(); start = UtilTsMillis();
memset(&conf, 0, sizeof(conf)); memset(&conf, 0, sizeof(conf));
@ -124,8 +115,11 @@ Main(Array *args, HashMap *env)
); );
ParseePrintASCII(); ParseePrintASCII();
Log(LOG_INFO, "======================="); Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)"); Log(LOG_INFO, "(This program is free software, see LICENSE.)");
#ifdef JANET
Log(LOG_INFO, "**Built with Janet!**");
#endif
#ifdef PLATFORM_IPHONE #ifdef PLATFORM_IPHONE
Log(LOG_WARNING, "Wait. Are you running this on an iPhone?"); Log(LOG_WARNING, "Wait. Are you running this on an iPhone?");
@ -223,11 +217,9 @@ Main(Array *args, HashMap *env)
Log(LOG_NOTICE, "Connecting to XMPP..."); Log(LOG_NOTICE, "Connecting to XMPP...");
jabber = XMPPInitialiseCompStream( jabber = XMPPInitialiseCompStream(
parsee_conf->component_addr,
parsee_conf->component_host, parsee_conf->component_host,
parsee_conf->component_port parsee_conf->component_port
); );
Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber);
if (!XMPPAuthenticateCompStream( if (!XMPPAuthenticateCompStream(
jabber, jabber,
parsee_conf->shared_comp_secret parsee_conf->shared_comp_secret
@ -287,18 +279,22 @@ Main(Array *args, HashMap *env)
Log(LOG_DEBUG, "Verbosity level: %d", verbose); Log(LOG_DEBUG, "Verbosity level: %d", verbose);
((ParseeData *) conf.handlerArgs)->verbosity = 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..."); Log(LOG_NOTICE, "Setting up local Matrix user...");
if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart))
{ {
char *parsee = ParseeMXID(conf.handlerArgs); char *parsee = ParseeMXID(conf.handlerArgs);
/* TODO: An hardcoded avatar like this sucks. */
ASSetAvatar(parsee_conf, ASSetAvatar(parsee_conf,
parsee, parsee,
"mxc://tedomum.net/" "mxc://tedomum.net/"
"7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136"
); );
ASSetName(parsee_conf, parsee, "Parsee bridge"); ASSetName(parsee_conf, parsee, "Parsee bridge");
Free(parsee); Free(parsee);
} }
@ -350,6 +346,8 @@ end:
ParseeDestroyHeadTable(); ParseeDestroyHeadTable();
ParseeDestroyJIDTable(); ParseeDestroyJIDTable();
ExtensionDestroyContext(ext_ctx);
(void) env; (void) env;
return 0; return 0;
} }

View file

@ -21,11 +21,13 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
Unistr *uninick = UnistrCreate(name); Unistr *uninick = UnistrCreate(name);
Unistr *filtered = UnistrFilter(uninick, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */ Unistr *filtered = UnistrFilter(uninick, UnistrIsBMP);
char *nick = UnistrC(filtered); char *nick = UnistrC(filtered);
char *rev = StrConcat(3, muc, "/", nick); char *rev = StrConcat(3, muc, "/", nick);
int nonce = 0; int nonce = 0;
Log(LOG_DEBUG, "MUCJOINER: filtered '%s' to '%s'", name, nick);
UnistrFree(uninick); UnistrFree(uninick);
UnistrFree(filtered); UnistrFree(filtered);
@ -100,7 +102,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
char *jid = ParseeEncodeMXID(state_key); char *jid = ParseeEncodeMXID(state_key);
char *sha = NULL, *mime = NULL; char *sha = NULL, *mime = NULL;
char *avatar = ASGetAvatar(data->config, NULL, state_key); char *avatar = ASGetAvatar(data->config, NULL, state_key);
char *url = ParseeToUnauth(data, avatar, NULL); char *url = ParseeToUnauth(data, avatar);
chat_id = ParseeGetFromRoomID(data, room_id); chat_id = ParseeGetFromRoomID(data, room_id);
ASGetMIMESHA(data->config, avatar, &mime, &sha); ASGetMIMESHA(data->config, avatar, &mime, &sha);
@ -255,9 +257,14 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
return; return;
} }
if (!body || *body != '!') if (*body != '!')
{ {
/* All commands are to be marked with a ! */ /* All commands are to be marked with a ! */
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("Please enter a valid command"), 0
));
Free(profile); Free(profile);
return; return;
} }
@ -289,10 +296,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
char *room_id = GrabString(event, 1, "room_id"); char *room_id = GrabString(event, 1, "room_id");
char *matrix_sender = GrabString(event, 1, "sender"); char *matrix_sender = GrabString(event, 1, "sender");
char *chat_id = NULL, *muc_id = NULL; char *chat_id = NULL, *muc_id = NULL;
char *user = NULL; char *user;
DbRef *room_data = NULL; DbRef *room_data;
HashMap *data_json = NULL; HashMap *data_json;
bool direct = false; bool direct = false;
if (!data || !event || !from || !to) if (!data || !event || !from || !to)
@ -324,8 +331,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
} }
else else
{ {
char *matrix_name = NULL, *matrix_avatar = NULL; char *matrix_name, *matrix_avatar;
char *mime = NULL, *sha = NULL; char *mime, *sha;
muc_id = ParseeGetMUCID(data, chat_id); muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id) if (!chat_id)
@ -358,47 +365,28 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
static void static void
ParseeMessageHandler(ParseeData *data, HashMap *event) ParseeMessageHandler(ParseeData *data, HashMap *event)
{ {
if (!data || !event)
{
return;
}
XMPPComponent *jabber = data->jabber; XMPPComponent *jabber = data->jabber;
StanzaBuilder *builder = NULL; StanzaBuilder *builder;
DbRef *ref = NULL; DbRef *ref = NULL;
HashMap *json = NULL; HashMap *json;
char *msgtype = GrabString(event, 2, "content", "msgtype");
char *m_sender = GrabString(event, 1, "sender");
char *unedited_id = NULL;
char *body = GrabString(event, 2, "content", "body"); char *body = GrabString(event, 2, "content", "body");
char *id = GrabString(event, 1, "room_id"); char *id = GrabString(event, 1, "room_id");
char *ev_id = GrabString(event, 1, "event_id"); char *ev_id = GrabString(event, 1, "event_id");
char *chat_id = NULL, *muc_id = NULL; char *m_sender = GrabString(event, 1, "sender");
char *chat_id, *muc_id;
char *reply_id = MatrixGetReply(event); char *reply_id = MatrixGetReply(event);
char *xepd = ParseeXMPPify(data, event); char *xepd = ParseeXMPPify(event);
char *type, *user, *xmppified_user = NULL, *to = NULL; char *type, *user, *xmppified_user = NULL, *to = NULL;
char *unauth = NULL; char *unauth = NULL;
char *origin_id = NULL, *stanza = NULL; char *origin_id = NULL, *stanza = NULL;
char *sender = NULL; char *sender = NULL;
char *unedited_id = MatrixGetEdit(event);
char *url = GrabString(event, 2, "content", "url"); char *url = GrabString(event, 2, "content", "url");
char *encoded_from = NULL; char *encoded_from = NULL;
bool direct = false; bool direct = false;
unedited_id = MatrixGetEdit(event);
if (unedited_id)
{
char *new_content = GrabString(event, 3, "content", "m.new_content", "body");
if (new_content) body = new_content;
}
if (data->config->ignore_bots && StrEquals(msgtype, "m.notice"))
{
Free(reply_id);
Free(xepd);
Free(unedited_id);
return;
}
if (ParseeIsPuppet(data->config, m_sender) || if (ParseeIsPuppet(data->config, m_sender) ||
ParseeManageBan(data, m_sender, id)) ParseeManageBan(data, m_sender, id))
{ {
@ -429,7 +417,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
type = direct ? "chat" : "groupchat"; type = direct ? "chat" : "groupchat";
user = GrabString(json, 1, "xmpp_user"); user = GrabString(json, 1, "xmpp_user");
unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename")); unauth = ParseeToUnauth(data, url);
encoded_from = ParseeEncodeMXID(m_sender); encoded_from = ParseeEncodeMXID(m_sender);
xmppified_user = StrConcat(3, xmppified_user = StrConcat(3,
@ -466,7 +454,6 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
Free(name); Free(name);
Free(avatar); Free(avatar);
} }
if (reply_id) if (reply_id)
{ {
/* TODO: Monocles chat DM users HATE this trick! /* TODO: Monocles chat DM users HATE this trick!
@ -523,8 +510,8 @@ end:
Free(stanza); Free(stanza);
Free(sender); Free(sender);
Free(unauth); Free(unauth);
Free(encoded_from);
Free(unedited_id); Free(unedited_id);
Free(encoded_from);
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
ref = NULL; ref = NULL;

View file

@ -1,9 +1,6 @@
#include <Matrix.h> #include <Matrix.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h> #include <string.h>
@ -35,34 +32,3 @@ MatrixParseID(char *user)
return ret; return ret;
} }
UserID *
MatrixParseIDFromMTO(Uri *uri)
{
UserID *id = NULL;
char *path, *params, *decoded;
if (!uri)
{
return NULL;
}
if (!StrEquals(uri->proto, "https") || !StrEquals(uri->host, "matrix.to"))
{
return NULL;
}
if (strncmp(uri->path, "/#/", 3))
{
return NULL;
}
path = StrDuplicate(uri->path + 3);
params = path ? strchr(path, '?') : NULL;
if (params)
{
*params = '\0';
}
decoded = HttpUrlDecode(path);
id = MatrixParseID(decoded);
Free(decoded);
Free(path);
return id;
}

View file

@ -36,12 +36,6 @@ ParseeConfigLoad(char *conf)
return; return;
} }
json = JsonDecode(stream); json = JsonDecode(stream);
if (!json)
{
Log(LOG_ERR, "Could not parse config JSON");
StreamClose(stream);
return;
}
config = Malloc(sizeof(*config)); config = Malloc(sizeof(*config));
#define CopyToStr(to, str) config->to = StrDuplicate( \ #define CopyToStr(to, str) config->to = StrDuplicate( \
@ -50,9 +44,6 @@ ParseeConfigLoad(char *conf)
#define CopyToInt(to, str) config->to = (int) ( \ #define CopyToInt(to, str) config->to = (int) ( \
JsonValueAsInteger(HashMapGet(json, str)) \ JsonValueAsInteger(HashMapGet(json, str)) \
) )
#define CopyToBool(to, str) config->to = (int) ( \
JsonValueAsBoolean(HashMapGet(json, str)) \
)
config->http_threads = 8; config->http_threads = 8;
config->xmpp_threads = 8; config->xmpp_threads = 8;
@ -67,26 +58,19 @@ ParseeConfigLoad(char *conf)
CopyToStr(server_base, "hs_base"); CopyToStr(server_base, "hs_base");
CopyToStr(homeserver_host, "hs_host"); CopyToStr(homeserver_host, "hs_host");
CopyToInt(homeserver_port, "hs_port"); CopyToInt(homeserver_port, "hs_port");
CopyToBool(homeserver_tls, "hs_tls");
if (!HashMapGet(json, "hs_tls"))
{
config->homeserver_tls = true;
}
CopyToBool(accept_pings, "accept_pings");
CopyToInt(component_port, "component_port"); CopyToInt(component_port, "component_port");
CopyToStr(component_addr, "component_addr");
CopyToStr(component_host, "component_host"); CopyToStr(component_host, "component_host");
CopyToStr(shared_comp_secret, "shared_secret"); CopyToStr(shared_comp_secret, "shared_secret");
CopyToInt(max_stanza_size, "max_stanza_size"); CopyToInt(max_stanza_size, "max_stanza_size");
CopyToStr(extensions, "extensions");
if (!config->max_stanza_size) if (!config->max_stanza_size)
{ {
/* Standard XMPP "minimum" maximum */ /* Standard XMPP "minimum" maximum */
config->max_stanza_size = 10000; config->max_stanza_size = 10000;
} }
CopyToBool(ignore_bots, "ignore_bots");
CopyToStr(media_base, "media_base"); CopyToStr(media_base, "media_base");
CopyToStr(db_path, "db"); CopyToStr(db_path, "db");
@ -147,7 +131,6 @@ ParseeConfigFree(void)
return; return;
} }
Free(config->component_host); Free(config->component_host);
Free(config->component_addr);
Free(config->shared_comp_secret); Free(config->shared_comp_secret);
Free(config->db_path); Free(config->db_path);
Free(config->homeserver_host); Free(config->homeserver_host);
@ -159,6 +142,7 @@ ParseeConfigFree(void)
Free(config->namespace_base); Free(config->namespace_base);
Free(config->media_base); Free(config->media_base);
Free(config->listen_as); Free(config->listen_as);
Free(config->extensions);
Free(config); Free(config);
} }
const ParseeConfig * const ParseeConfig *

View file

@ -27,7 +27,6 @@ ParseeInitData(XMPPComponent *comp)
data->config = ParseeConfigGet(); data->config = ParseeConfigGet();
data->router = HttpRouterCreate(); data->router = HttpRouterCreate();
data->jabber = comp; data->jabber = comp;
data->muc = CreateMUCServer(data);
data->handler = CommandCreateRouter(); data->handler = CommandCreateRouter();
data->oid_servers = HashMapCreate(); data->oid_servers = HashMapCreate();
@ -117,7 +116,6 @@ ParseeFreeData(ParseeData *data)
pthread_mutex_destroy(&data->halt_lock); pthread_mutex_destroy(&data->halt_lock);
Free(data->id); Free(data->id);
XMPPEndCompStream(data->jabber); XMPPEndCompStream(data->jabber);
FreeMUCServer(data->muc);
DbClose(data->db); DbClose(data->db);
HttpRouterFree(data->router); HttpRouterFree(data->router);
CommandFreeRouter(data->handler); CommandFreeRouter(data->handler);
@ -132,6 +130,7 @@ ParseeCleanup(void *datp)
char *chat; char *chat;
size_t i; size_t i;
uint64_t ts = UtilTsMillis(); uint64_t ts = UtilTsMillis();
size_t entries = 0;
chats = DbList(data->db, 1, "chats"); chats = DbList(data->db, 1, "chats");
@ -175,6 +174,7 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \ if (cleaned > threshold) \
{ \ { \
DbDelete(data->db, 4, "chats", chat, #field"s", field); \ DbDelete(data->db, 4, "chats", chat, #field"s", field); \
entries++; \
} \ } \
Free(field); \ Free(field); \
} \ } \
@ -231,6 +231,7 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \ if (cleaned > threshold) \
{ \ { \
JsonValueFree(HashMapDelete(field##s, field)); \ JsonValueFree(HashMapDelete(field##s, field)); \
entries++; \
} \ } \
Free(field); \ Free(field); \
} \ } \
@ -725,3 +726,13 @@ ParseeIsMediaEnabled(ParseeData *data, char *chat_id)
return ret; return ret;
} }
bool
ParseeIsMUC(ParseeData *data, char *jid)
{
if (!data)
{
return false;
}
return XMPPQueryMUC(data->jabber, jid, NULL);
}

View file

@ -1,7 +1,6 @@
#include <Parsee.h> #include <Parsee.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h> #include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
@ -539,6 +538,7 @@ ParseeGetMUCID(ParseeData *data, char *chat_id)
return ret; return ret;
} }
void void
ParseeSendPresence(ParseeData *data) ParseeSendPresence(ParseeData *data)
{ {
@ -562,7 +562,7 @@ ParseeSendPresence(ParseeData *data)
uint64_t ts = GrabInteger(DbJson(chat), 1, "ts"); uint64_t ts = GrabInteger(DbJson(chat), 1, "ts");
int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1; int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1;
/* Make a fake user join the MUC */ /* Make a fake user join the MUC */
Log(LOG_NOTICE, "Sending presence to %s", rev); Log(LOG_NOTICE, "Sending presence to %s last=%ds", rev, diff);
XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false); XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false);
DbUnlock(data->db, chat); DbUnlock(data->db, chat);
@ -688,13 +688,12 @@ end:
#include <Cytoplasm/Uri.h> #include <Cytoplasm/Uri.h>
char * char *
ParseeToUnauth(ParseeData *data, char *mxc, char *filename) ParseeToUnauth(ParseeData *data, char *mxc)
{ {
Uri *url = NULL; Uri *url = NULL;
char *ret; char *ret;
char *key, *hmac; char *key, *hmac;
#define PAT "%s/media/%s%s?hmac=%s" #define PAT "%s/_matrix/client/v1/media/download/%s%s?hmac=%s"
#define PATF "%s/media/%s%s/%s?hmac=%s"
size_t l; size_t l;
if (!data || !mxc) if (!data || !mxc)
{ {
@ -715,45 +714,19 @@ ParseeToUnauth(ParseeData *data, char *mxc, char *filename)
hmac = ParseeHMACS(data->id, key); hmac = ParseeHMACS(data->id, key);
Free(key); Free(key);
if (!filename)
{
l = snprintf(NULL, 0, l = snprintf(NULL, 0,
PAT, PAT,
data->config->media_base, data->config->media_base,
url->host, url->path, url->host, url->path,
hmac hmac
); );
}
else
{
char *encoded = HttpUrlEncode(filename);
l = snprintf(NULL, 0,
PATF,
data->config->media_base,
url->host, url->path, encoded,
hmac
);
Free(encoded);
}
ret = Malloc(l + 3); ret = Malloc(l + 3);
if (!filename)
{
snprintf(ret, l + 1, snprintf(ret, l + 1,
PAT, PAT,
data->config->media_base, data->config->media_base,
url->host, url->path, url->host, url->path,
hmac hmac
); );
}
else
{
snprintf(ret, l + 1,
PATF,
data->config->media_base,
url->host, url->path, filename,
hmac
);
}
UriFree(url); UriFree(url);
Free(hmac); Free(hmac);
return ret; return ret;

View file

@ -5,14 +5,11 @@
#include <Cytoplasm/Http.h> #include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/Log.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <StringStream.h> #include <StringStream.h>
#include <Matrix.h>
#include <XML.h> #include <XML.h>
#include <AS.h> #include <AS.h>
@ -20,7 +17,7 @@ typedef struct XMPPFlags {
bool quote; bool quote;
} XMPPFlags; } XMPPFlags;
static char * static char *
XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags) XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
{ {
char *xepd = NULL, *tmp = NULL; char *xepd = NULL, *tmp = NULL;
@ -58,7 +55,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
} \ } \
} \ } \
while (0) while (0)
switch (elem ? elem->type : -1) switch (elem->type)
{ {
case XML_ELEMENT_DATA: case XML_ELEMENT_DATA:
Concat(elem->data); Concat(elem->data);
@ -70,7 +67,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
@ -83,7 +80,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
@ -96,7 +93,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
@ -129,7 +126,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
@ -144,70 +141,36 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
} }
if (i != 0)
{
Concat("\n"); Concat("\n");
} }
}
else if (StrEquals(elem->name, "a")) else if (StrEquals(elem->name, "a"))
{ {
char *href = HashMapGet(elem->attrs, "href"); char *href = HashMapGet(elem->attrs, "href");
Uri *pref = UriParse(href);
if (pref && StrEquals(pref->host, "matrix.to"))
{
/* TODO: Check if the element here is a Matrix.TO /* TODO: Check if the element here is a Matrix.TO
* pointing to a Parsee user. */ * pointing to a Parsee user. */
UserID *id = MatrixParseIDFromMTO(pref);
if (id)
{
char *real_id = StrConcat(4, "@", id->localpart, ":", id->server);
/* TODO: Detect if it already is a Parsee user */
if (ParseeIsPuppet(conf, real_id))
{
char *name = ASGetName(conf, NULL, real_id);
Concat((name ? name : real_id));
Free(name);
}
else
{
Concat(real_id);
}
Free(real_id);
}
else
{
Concat(href);
}
Free(id);
}
else
{
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
} }
Concat(" < "); Concat("< ");
Concat(href); Concat(href);
Concat(" >"); Concat(" >");
} }
UriFree(pref);
}
else else
{ {
for (i = 0; i < ArraySize(elem->children); i++) for (i = 0; i < ArraySize(elem->children); i++)
{ {
child = ArrayGet(elem->children, i); child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags); subxep = XMPPifyElement(event, child, flags);
Concat(subxep); Concat(subxep);
Free(subxep); Free(subxep);
@ -219,45 +182,8 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF
} }
return xepd; return xepd;
} }
static char *
GetRawBody(HashMap *event)
{
void *id;
if ((id = MatrixGetEdit(event)))
{
char *new = GrabString(event, 3, "content", "m.new_content", "body");
Free(id);
if (new)
{
return new;
}
}
return GrabString(event, 2, "content", "body");
}
static char *
GetHTMLBody(HashMap *event)
{
if (MatrixGetEdit(event))
{
char *new = GrabString(event, 3, "content", "m.new_content", "formatted_body");
if (new)
{
return new;
}
}
return GrabString(event, 2, "content", "formatted_body");
}
static char *
GetBodyFormat(HashMap *event)
{
if (MatrixGetEdit(event))
{
return GrabString(event, 3, "content", "m.new_content", "format");
}
return GrabString(event, 2, "content", "format");
}
char * char *
ParseeXMPPify(ParseeData *data, HashMap *event) ParseeXMPPify(HashMap *event)
{ {
char *type, *format, *html; char *type, *format, *html;
char *xepd = NULL; char *xepd = NULL;
@ -276,28 +202,20 @@ ParseeXMPPify(ParseeData *data, HashMap *event)
return NULL; return NULL;
} }
if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html")) format = JsonValueAsString(JsonGet(event, 2, "content", "format"));
if (!StrEquals(format, "org.matrix.custom.html"))
{ {
/* Settle for the raw body instead. */ /* Settle for the raw body instead. */
char *body = GetRawBody(event); char *body = JsonValueAsString(JsonGet(event, 2, "content", "body"));
return StrDuplicate(body); return StrDuplicate(body);
} }
html = GetHTMLBody(event); html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body"));
html = StrConcat(3, "<html>", html, "</html>"); html = StrConcat(3, "<html>", html, "</html>");
elem = XMLCDecode(StrStreamReader(html), true, true); elem = XMLCDecode(StrStreamReader(html), true, true);
if (!elem)
{
/* Settle for the raw body instead.
* TODO: Have the parser be more leinent on errors in HTML mode. */
char *body = GetRawBody(event);
Free(html);
return StrDuplicate(body);
}
flags.quote = false; flags.quote = false;
xepd = XMPPifyElement(data ? data->config : NULL, event, elem, flags); xepd = XMPPifyElement(event, elem, flags);
XMLFreeElement(elem); XMLFreeElement(elem);
Free(html); Free(html);

View file

@ -6,28 +6,6 @@
#include <Glob.h> #include <Glob.h>
#include <AS.h> #include <AS.h>
void
ParseeGlobalUnban(ParseeData *data, char *glob)
{
DbRef *ref;
HashMap *j;
if (!data || !glob)
{
return;
}
ref = DbLock(data->db, 1, "global_bans");
if (!ref)
{
ref = DbCreate(data->db, 1, "global_bans");
}
j = DbJson(ref);
JsonValueFree(HashMapDelete(j, glob));
DbUnlock(data->db, ref);
}
void void
ParseeGlobalBan(ParseeData *data, char *glob, char *reason) ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
{ {

View file

@ -10,37 +10,6 @@
#include <string.h> #include <string.h>
static HttpClientContext *
TryDownload(ParseeData *data, char *server, char *identi)
{
HttpClientContext *cctx;
char *path;
server = HttpUrlEncode(server);
identi = HttpUrlEncode(identi);
path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi);
cctx = ParseeCreateRequest(data->config, HTTP_GET, path);
ASAuthenticateRequest(data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
if (HttpRequestSend(cctx) != HTTP_OK)
{
Log(LOG_WARNING, "Failing back.");
HttpClientContextFree(cctx);
path = StrConcat(4, "/_matrix/client/v1/media/download/", server, "/", identi);
cctx = ParseeCreateRequest(data->config, HTTP_GET, path);
ASAuthenticateRequest(data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
}
Free(server);
Free(identi);
return cctx;
}
RouteHead(RouteMedia, arr, argp) RouteHead(RouteMedia, arr, argp)
{ {
ParseeHttpArg *args = argp; ParseeHttpArg *args = argp;
@ -48,7 +17,7 @@ RouteHead(RouteMedia, arr, argp)
HashMap *reqh, *params; HashMap *reqh, *params;
char *server = ArrayGet(arr, 0); char *server = ArrayGet(arr, 0);
char *identi = ArrayGet(arr, 1); char *identi = ArrayGet(arr, 1);
char *key, *val; char *path, *key, *val;
char *hmac, *chkmak = NULL; char *hmac, *chkmak = NULL;
params = HttpRequestParams(args->ctx); params = HttpRequestParams(args->ctx);
@ -75,7 +44,15 @@ RouteHead(RouteMedia, arr, argp)
/* Proxy the media through an authenticated endpoint if the HMAC /* Proxy the media through an authenticated endpoint if the HMAC
* is valid. */ * is valid. */
cctx = TryDownload(args->data, server, identi); server = HttpUrlEncode(server);
identi = HttpUrlEncode(identi);
path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi);
cctx = ParseeCreateRequest(args->data->config, HTTP_GET, path);
ASAuthenticateRequest(args->data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
reqh = HttpResponseHeaders(cctx); reqh = HttpResponseHeaders(cctx);
while (HashMapIterate(reqh, &key, (void **) &val)) while (HashMapIterate(reqh, &key, (void **) &val))
{ {
@ -88,6 +65,8 @@ RouteHead(RouteMedia, arr, argp)
} }
HttpClientContextFree(cctx); HttpClientContextFree(cctx);
Free(server);
Free(identi);
return NULL; return NULL;
} }

View file

@ -10,6 +10,8 @@ RouteHead(RoutePing, arr, argp)
ParseeHttpArg *args = argp; ParseeHttpArg *args = argp;
HashMap *request = NULL; HashMap *request = NULL;
HashMap *response = NULL; HashMap *response = NULL;
Array *events;
size_t i;
response = ASVerifyRequest(args); response = ASVerifyRequest(args);
if (response) if (response)

View file

@ -34,6 +34,10 @@ RouteHead(RouteTxns, arr, argp)
for (i = 0; i < ArraySize(events); i++) for (i = 0; i < ArraySize(events); i++)
{ {
HashMap *event = JsonValueAsObject(ArrayGet(events, i)); HashMap *event = JsonValueAsObject(ArrayGet(events, i));
if (ExtensionPushEvent(args->data->exts, event))
{
continue;
}
ParseeEventHandler(args->data, event); ParseeEventHandler(args->data, event);
} }

View file

@ -26,6 +26,11 @@ SignalHandler(int signal)
HttpServerStop(data->server); HttpServerStop(data->server);
return; return;
} }
if (signal == SIGPIPE)
{
Log(LOG_DEBUG, "Caught a SIGPIPE...");
return;
}
} }
bool bool
@ -41,7 +46,7 @@ ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp)
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;
#define Register(act) (sigaction(act, &sa, NULL) >= 0) #define Register(act) (sigaction(act, &sa, NULL) >= 0)
if (!Register(SIGTERM) || !Register(SIGINT)) if (!Register(SIGTERM) || !Register(SIGINT) || !Register(SIGPIPE))
{ {
Log(LOG_ERR, "Couldn't register signals..."); Log(LOG_ERR, "Couldn't register signals...");
return false; return false;

View file

@ -192,16 +192,6 @@ UnistrGetch(Unistr *unistr, size_t i)
return i < unistr->length ? unistr->codepoints[i] : 0; return i < unistr->length ? unistr->codepoints[i] : 0;
} }
bool bool
UnistrIsASCII(uint32_t u)
{
if (u == 0)
{
return NULL;
}
return u < 0x7F;
}
bool
UnistrIsBMP(uint32_t u) UnistrIsBMP(uint32_t u)
{ {
if (u == 0) if (u == 0)
@ -277,38 +267,3 @@ UnistrGetOffset(Unistr *str, uint32_t sep)
} }
return 0; return 0;
} }
size_t
UnistrGetUTFOffset(char *cstr, size_t unicode)
{
Unistr *tmp;
size_t ret = 0;
if (!cstr)
{
return 0;
}
tmp = UnistrCreate(cstr);
for (size_t i = 0; i < unicode && i < tmp->length; i++)
{
uint32_t codepoint = tmp->codepoints[i];
if (codepoint >= 0x0000 && codepoint <= 0x007F)
{
ret += 1;
}
else if (codepoint >= 0x0080 && codepoint <= 0x07FF)
{
ret += 2;
}
else if (codepoint >= 0x0800 && codepoint <= 0xFFFF)
{
ret += 3;
}
else if (codepoint >= 0x010000 && codepoint <= 0x10FFFF)
{
ret += 4;
}
}
end:
Free(tmp);
return ret;
}

View file

@ -329,8 +329,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
break; break;
case XEP393_MONO: case XEP393_MONO:
head = XMLCreateTag("code"); head = XMLCreateTag("code");
XMLAddChild(xmlparent, XMLCreateText("`"));
XMLAddChild(xmlparent, head); XMLAddChild(xmlparent, head);
XMLAddChild(head, XMLCreateText("`"));
break; break;
case XEP393_SRKE: case XEP393_SRKE:
head = XMLCreateTag("s"); head = XMLCreateTag("s");
@ -372,7 +372,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
XMLAddChild(head, XMLCreateText("_")); XMLAddChild(head, XMLCreateText("_"));
break; break;
case XEP393_MONO: case XEP393_MONO:
XMLAddChild(xmlparent, XMLCreateText("`")); XMLAddChild(head, XMLCreateText("`"));
break; break;
case XEP393_SRKE: case XEP393_SRKE:
XMLAddChild(head, XMLCreateText("~")); XMLAddChild(head, XMLCreateText("~"));

View file

@ -31,12 +31,6 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
bool flag = false; bool flag = false;
switch (event->type) switch (event->type)
{ {
case XML_ERROR:
XMLFreeEvent(event);
XMLFreeElement(ret);
ArrayFree(stack);
XMLFreeLexer(lexer);
return NULL;
case XML_LEXER_STARTELEM: case XML_LEXER_STARTELEM:
/* Create a new element that will populated. */ /* Create a new element that will populated. */
top = XMLCreateTag(event->element); top = XMLCreateTag(event->element);

View file

@ -58,7 +58,6 @@ static char * XMLPopElement(XMLexer *lexer);
static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateRelax(XMLexer *lexer); static XMLEvent * XMLCreateRelax(XMLexer *lexer);
static XMLEvent * XMLCreateError(XMLexer *lexer);
static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end); static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end);
static XMLEvent * XMLCreateData(XMLexer *lexer); static XMLEvent * XMLCreateData(XMLexer *lexer);
@ -199,9 +198,7 @@ XMLCrank(XMLexer *lexer)
else if (XMLookahead(lexer, "--", false)) else if (XMLookahead(lexer, "--", false))
{ {
/* Throw error */ /* Throw error */
XMLFreeEvent(event); return NULL;
event = XMLCreateError(lexer);
break;
} }
break; break;
case XML_STATE_PI: case XML_STATE_PI:
@ -218,9 +215,6 @@ XMLCrank(XMLexer *lexer)
if (!attrname) if (!attrname)
{ {
/* TODO: Throw error */ /* TODO: Throw error */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
} }
XMLPushElement(lexer, attrname); XMLPushElement(lexer, attrname);
@ -247,10 +241,7 @@ XMLCrank(XMLexer *lexer)
} }
else if (XMLookahead(lexer, "'", true)) else if (XMLookahead(lexer, "'", true))
{ {
//while (true); uh oh while (true);
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
} }
break; break;
case XML_STATE_ATTRTAIL: case XML_STATE_ATTRTAIL:
@ -259,8 +250,6 @@ XMLCrank(XMLexer *lexer)
if (!XMLookahead(lexer, ">", true)) if (!XMLookahead(lexer, ">", true))
{ {
/* TODO: Throw error. */ /* TODO: Throw error. */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break; break;
} }
lexer->state = XML_STATE_NONE; lexer->state = XML_STATE_NONE;
@ -269,8 +258,6 @@ XMLCrank(XMLexer *lexer)
break; break;
default: default:
/* TODO */ /* TODO */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break; break;
} }
/* TODO: Crank our XML parser. */ /* TODO: Crank our XML parser. */
@ -706,26 +693,6 @@ XMLCreateData(XMLexer *lexer)
return event; return event;
} }
static XMLEvent * static XMLEvent *
XMLCreateError(XMLexer *lexer)
{
XMLEvent *event = Malloc(sizeof(*event));
size_t elements = ArraySize(lexer->data.elements);
event->type = XML_ERROR;
event->element = elements ?
StrDuplicate(ArrayGet(lexer->data.elements, elements - 1)) :
NULL;
event->attrs = NULL;
event->data = NULL;
/* TODO */
event->line = 0;
event->col = 0;
event->offset = 0;
return event;
}
static XMLEvent *
XMLCreateRelax(XMLexer *lexer) XMLCreateRelax(XMLexer *lexer)
{ {
XMLEvent *event = Malloc(sizeof(*event)); XMLEvent *event = Malloc(sizeof(*event));

View file

@ -19,7 +19,7 @@
#define DEFAULT_PROSODY_PORT 5347 #define DEFAULT_PROSODY_PORT 5347
XMPPComponent * XMPPComponent *
XMPPInitialiseCompStream(char *addr, char *host, int port) XMPPInitialiseCompStream(char *host, int port)
{ {
int sd = -1; int sd = -1;
struct addrinfo hints, *res, *res0; struct addrinfo hints, *res, *res0;
@ -28,17 +28,12 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
Stream *stream; Stream *stream;
XMPPComponent *comp; XMPPComponent *comp;
if (!addr)
{
addr = host;
}
snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT); snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT);
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(addr, serv, &hints, &res0); error = getaddrinfo(host, serv, &hints, &res0);
if (error) if (error)
{ {
const char *error_str = gai_strerror(error); const char *error_str = gai_strerror(error);
@ -61,10 +56,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0) if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{ {
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, strerror(errno)
);
close(sd); close(sd);
sd = -1; sd = -1;
continue; continue;
@ -75,10 +66,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
if (sd < 0) if (sd < 0)
{ {
Log(LOG_ERR,
"%s: cannot connect to '%s': no socket available", __func__,
host
);
return NULL; return NULL;
} }
freeaddrinfo(res0); freeaddrinfo(res0);
@ -86,10 +73,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
stream = StreamFd(sd); stream = StreamFd(sd);
if (!stream) if (!stream)
{ {
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, "couldn't create a Cytoplasm stream"
);
close(sd); close(sd);
return NULL; return NULL;
} }
@ -182,49 +165,6 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
{ {
Log(LOG_ERR, "Excepted empty handshake reply, got nonsense."); Log(LOG_ERR, "Excepted empty handshake reply, got nonsense.");
Log(LOG_ERR, "Another service (possibly Parsee) may have taken over."); Log(LOG_ERR, "Another service (possibly Parsee) may have taken over.");
while ((ev = XMLCrank(sax)))
{
char *key, *val;
switch (ev->type)
{
case XML_LEXER_STARTELEM:
Log(LOG_DEBUG, "<%s>", ev->element);
LogConfigIndent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
/* TODO: Log out attributes a little better */
while (HashMapIterate(ev->attrs, &key, (void **) &val))
{
Log(LOG_DEBUG, "(%s=%s)", key, val);
}
LogConfigUnindent(LogConfigGlobal());
LogConfigUnindent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
break;
case XML_LEXER_ELEM:
Log(LOG_DEBUG, "<%s/>", ev->element);
LogConfigIndent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
/* TODO: Log out attributes a little better */
while (HashMapIterate(ev->attrs, &key, (void **) &val))
{
Log(LOG_DEBUG, "(%s=%s)", key, val);
}
LogConfigUnindent(LogConfigGlobal());
LogConfigUnindent(LogConfigGlobal());
break;
case XML_LEXER_ENDELEM:
LogConfigUnindent(LogConfigGlobal());
Log(LOG_DEBUG, "</%s>", ev->element);
break;
case XML_LEXER_DATA:
Log(LOG_DEBUG, "%s", ev->data);
break;
}
XMLFreeEvent(ev);
}
LogConfigIndentSet(LogConfigGlobal(), 0);
Log(LOG_ERR, ""); Log(LOG_ERR, "");
Log(LOG_ERR, "Simply jealous of that other service..."); Log(LOG_ERR, "Simply jealous of that other service...");
Free(stream_id); Free(stream_id);

View file

@ -8,8 +8,6 @@
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
#include "XMPPThread/internal.h"
bool bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{ {
@ -44,7 +42,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
Free(uuid); Free(uuid);
if (!iq_query || !StrEquals(iq_query->name, "iq")) if (!iq_query || !StrEquals(iq_query->name, "iq"))
{ {
Log(LOG_ERR, "Didn't receive an <iq> stanza");
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
return false; return false;
} }
@ -56,11 +53,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
"conference")) "conference"))
{ {
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
Log(LOG_DEBUG, "MUC INFO ERROR");
Log(LOG_DEBUG,
"identityp=%p category=%s", identity,
identity ? HashMapGet(identity->attrs, "category") : NULL
);
return false; return false;
} }
@ -170,7 +162,6 @@ bool
XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret) XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret)
{ {
XMLElement *presence, *x, *reply, *history, *photo; XMLElement *presence, *x, *reply, *history, *photo;
IQFeatures *features;
char *from, *id, *stime = "3600"; char *from, *id, *stime = "3600";
if (!comp || !fr || !muc) if (!comp || !fr || !muc)
{ {
@ -198,15 +189,7 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool
XMLAddChild(x, history); XMLAddChild(x, history);
XMLAddChild(presence, x); XMLAddChild(presence, x);
features = LookupJIDFeatures(from); XMPPAnnotatePresence(presence);
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
XMPPAnnotatePresence(presence, features);
FreeIQFeatures(features);
if (hash) if (hash)
{ {
@ -242,7 +225,6 @@ void
XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
{ {
XMLElement *presence; XMLElement *presence;
IQFeatures *features;
char *from, *id; char *from, *id;
if (!comp || !fr || !muc) if (!comp || !fr || !muc)
{ {
@ -264,16 +246,7 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
XMLAddChild(presence, status); XMLAddChild(presence, status);
} }
features = LookupJIDFeatures(from); XMPPAnnotatePresence(presence);
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
XMPPAnnotatePresence(presence, features);
FreeIQFeatures(features);
XMPPSendStanza(comp, presence, 10000); XMPPSendStanza(comp, presence, 10000);

View file

@ -1,154 +0,0 @@
#include <MUCServ.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <pthread.h>
#include <string.h>
#define MUCNS "http://jabber.org/protocol/muc"
struct MUCServer {
pthread_mutex_t mut;
ParseeData *data;
};
static char *
GetMUCName(char *jid)
{
char *name, *at;
size_t len;
if (!jid || *jid != '#')
{
return NULL;
}
jid++;
at = strchr(jid, '@');
if (!at)
{
return StrDuplicate(jid);
}
len = at - jid;
name = Malloc(len + 1);
memset(name, '\0', len + 1);
memcpy(name, jid, len);
return name;
}
MUCServer *
CreateMUCServer(ParseeData *data)
{
MUCServer *server;
if (!data)
{
return NULL;
}
server = Malloc(sizeof(*server));
pthread_mutex_init(&server->mut, NULL);
server->data = data;
return server;
}
static bool
MUCManagePresence(MUCServer *serv, XMLElement *stanza, char *from, char *to)
{
char *name;
XMLElement *x = XMLookForTKV(stanza, "x", "xmlns", MUCNS);
char *type = HashMapGet(stanza->attrs, "type");
if (x && !type)
{
/* The user is trying to join the MUC */
name = GetMUCName(to);
Log(LOG_WARNING, "%s is trying to join MUC '%s'", from, name);
/* TODO: Check if the user should be joining. If so, make them.
* Implementing MUCs is gonna be fun. */
/* TODO: Presence broadcast hell. */
Free(name);
}
return false;
}
static bool
MUCManageMessage(MUCServer *serv, XMLElement *stanza, char *from, char *to)
{
Log(LOG_WARNING, "MUCSERV: got a message %s->%s", from, to);
return false;
}
bool
ManageMUCStanza(MUCServer *serv, XMLElement *stanza)
{
char *stype, *from, *to;
bool ret;
if (!serv || !stanza)
{
return false;
}
from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to");
stype = stanza->name;
if (to && *to != '#')
{
/* We aren't interacting with a MUC at all. Don't do anything. */
return false;
}
if (StrEquals(stype, "iq"))
{
/* TODO: Worry about IQ later */
return false;
}
ret = false;
pthread_mutex_lock(&serv->mut);
if (StrEquals(stype, "presence"))
{
ret = MUCManagePresence(serv, stanza, from, to);
}
else if (StrEquals(stype, "message"))
{
ret = MUCManageMessage(serv, stanza, from, to);
}
/* TODO: Do stuff while locked */
pthread_mutex_unlock(&serv->mut);
/* TODO: Verify the destination, and make sure we aren't doing
* anything stupid(especially with discovery) */
(void) ret;
return false;
}
bool
MUCServerExists(MUCServer *serv, char *muc)
{
if (!serv || !muc)
{
return false;
}
return false;
}
void
FreeMUCServer(MUCServer *serv)
{
if (!serv)
{
return;
}
pthread_mutex_lock(&serv->mut);
/* TODO */
pthread_mutex_unlock(&serv->mut);
pthread_mutex_destroy(&serv->mut);
Free(serv);
}

View file

@ -7,8 +7,6 @@
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
#include <stdlib.h>
void void
XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact) XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
@ -161,31 +159,27 @@ XMPPGetReply(XMLElement *elem)
return HashMapGet(rep->attrs, "id"); return HashMapGet(rep->attrs, "id");
} }
ssize_t void
XMPPGetReplyOffset(XMLElement *elem) XMPPAnnotatePresence(XMLElement *presence)
{ {
if (!elem) XMLElement *c;
char *ver;
if (!presence)
{ {
return -1; return;
} }
for (size_t i = 0; i < ArraySize(elem->children); i++)
{ ver = XMPPGenerateVer();
XMLElement *child = ArrayGet(elem->children, i); c = XMLCreateTag("c");
char *xmlns = HashMapGet(child->attrs, "xmlns"); XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
char *xfor = HashMapGet(child->attrs, "for"); XMLAddAttr(c, "hash", "sha-1");
if (StrEquals(child->name, "fallback") && XMLAddAttr(c, "node", REPOSITORY);
StrEquals(xmlns, "urn:xmpp:feature-fallback:0") && XMLAddAttr(c, "ver", ver);
StrEquals(xfor, "urn:xmpp:reply:0"))
{ Free(ver);
XMLElement *body = XMLookForUnique(child, "body"); XMLAddChild(presence, c);
if (body && HashMapGet(body->attrs, "end"))
{
return strtol(HashMapGet(body->attrs, "end"), NULL, 10);
}
}
}
return -1;
} }
char * char *
XMPPGetModeration(XMLElement *stanza) XMPPGetModeration(XMLElement *stanza)
{ {

View file

@ -4,20 +4,10 @@
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
struct XMPPCommand { #include "XMPPCommand/Internal.h"
XMPPCmdCallback callback;
char *node, *name;
char *form_instruction;
char *form_title;
/* TODO: On-the-fly generation of options */
Array *options;
XMPPOptionWriter otf;
};
XMPPCommand * XMPPCommand *
XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback callback_funct)
{ {
XMPPCommand *cmd; XMPPCommand *cmd;
@ -32,6 +22,7 @@ XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct)
cmd->name = StrDuplicate(name); cmd->name = StrDuplicate(name);
cmd->form_instruction = NULL; cmd->form_instruction = NULL;
cmd->form_title = NULL; cmd->form_title = NULL;
cmd->flags = flags;
/* No options -> no form required */ /* No options -> no form required */
cmd->options = NULL; cmd->options = NULL;
@ -127,7 +118,7 @@ XMPPFormifyCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
ArrayFree(cmd->options); ArrayFree(cmd->options);
cmd->options = NULL; cmd->options = NULL;
cmd->otf(m, cmd, from); cmd->otf(m, cmd, from, cmd->node);
} }
if (!cmd->options) if (!cmd->options)
{ {
@ -184,13 +175,13 @@ XMPPCommandRequiresForm(XMPPCommand *cmd)
return cmd ? (cmd->otf || !!cmd->options) : false; return cmd ? (cmd->otf || !!cmd->options) : false;
} }
void 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; return;
} }
cmd->callback(m, from, form, to); cmd->callback(m, from, form, to, node);
} }
bool bool

View file

@ -0,0 +1,17 @@
#include <XMPPCommand.h>
#include <Cytoplasm/Array.h>
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;
};

View file

@ -8,6 +8,8 @@
#include <pthread.h> #include <pthread.h>
#include "XMPPCommand/Internal.h"
typedef struct XMPPSession { typedef struct XMPPSession {
char *identifier; char *identifier;
@ -323,7 +325,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddAttr(command_xml, "node", node); XMLAddAttr(command_xml, "node", node);
XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_id); 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); XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size); 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, "node", node);
XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_given); 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); XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size); XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
@ -385,3 +387,15 @@ XMPPGetManagerCookie(XMPPCommandManager *manager)
{ {
return manager ? manager->cookie : NULL; 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;
}

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -23,6 +23,8 @@ AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
GetFieldValue(glob, "glob", form); GetFieldValue(glob, "glob", form);
(void) node;
if (!ParseeIsAdmin(data, trimmed)) if (!ParseeIsAdmin(data, trimmed))
{ {
SetNote("error", "User is not authorised to execute command."); SetNote("error", "User is not authorised to execute command.");

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -21,6 +21,8 @@ AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
GetFieldValue(entity, "entity", form); GetFieldValue(entity, "entity", form);
GetFieldValue(reason, "reason", form); GetFieldValue(reason, "reason", form);
(void) node;
if (!ParseeIsAdmin(data, trimmed)) if (!ParseeIsAdmin(data, trimmed))
{ {
SetNote("error", "User is not authorised to execute command."); SetNote("error", "User is not authorised to execute command.");

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
size_t i; size_t i;
@ -20,6 +20,8 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLElement *title; XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt; XMLElement *reported, *item, *field, *value, *txt;
(void) node;
x = XMLCreateTag("x"); x = XMLCreateTag("x");
title = XMLCreateTag("title"); title = XMLCreateTag("title");

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); 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."); SetNote("info", "Parsee data was sucessfully cleant up.");
(void) form; (void) form;
(void) node;
} }

View file

@ -31,7 +31,7 @@ DelAdmin(Array *admin_list, char *glob)
return removed; return removed;
} }
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -84,9 +84,11 @@ DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
} }
SetNote("info", "Sucessfully removed admins"); SetNote("info", "Sucessfully removed admins");
/* TODO */ /* TODO */
(void) node;
} }
void void
FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from) FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from, char *node)
{ {
ParseeData *data = XMPPGetManagerCookie(m); ParseeData *data = XMPPGetManagerCookie(m);
DbRef *admins; DbRef *admins;
@ -120,4 +122,6 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
DbUnlock(data->db, admins); DbUnlock(data->db, admins);
XMPPAddOption(cmd, admin_opt); XMPPAddOption(cmd, admin_opt);
(void) node;
} }

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from); char *muc = ParseeTrimJID(from);
@ -29,9 +29,10 @@ MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
Free(room_id); Free(room_id);
Free(chat_id); Free(chat_id);
(void) form; (void) form;
(void) node;
} }
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from); char *muc = ParseeTrimJID(from);
@ -46,4 +47,5 @@ MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen
Free(msg); Free(msg);
Free(chat_id); Free(chat_id);
(void) form; (void) form;
(void) node;
} }

View file

@ -15,12 +15,12 @@
#include <AS.h> #include <AS.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL; char *affiliation, *role;
char *chat_id = NULL; char *chat_id;
char *muc = NULL; char *muc;
char *key = NULL, *val = NULL; char *key = NULL, *val = NULL;
@ -51,9 +51,10 @@ end:
Free(chat_id); Free(chat_id);
Free(muc); Free(muc);
(void) form; (void) form;
(void) node;
} }
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL; char *affiliation = NULL, *role = NULL;
@ -114,4 +115,5 @@ end:
Free(chat_id); Free(chat_id);
Free(muc); Free(muc);
(void) form; (void) form;
(void) node;
} }

View file

@ -14,7 +14,7 @@
#include <AS.h> #include <AS.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role; char *affiliation, *role;
@ -64,4 +64,5 @@ MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
Free(room); Free(room);
Free(muc); Free(muc);
(void) form; (void) form;
(void) node;
} }

View file

@ -14,7 +14,7 @@
#define TITLE "Parsee global bans" #define TITLE "Parsee global bans"
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -89,4 +89,5 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
} }
(void) form; (void) form;
(void) node;
} }

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); char *trimmed = ParseeTrimJID(from);
size_t alloc = MemoryAllocated(); size_t alloc = MemoryAllocated();
@ -69,5 +69,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLAddChild(out, x); XMLAddChild(out, x);
(void) form; (void) form;
(void) node;
(void) m; (void) m;
} }

View file

@ -12,7 +12,7 @@
#include <XML.h> #include <XML.h>
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -35,9 +35,10 @@ ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLE
SetNote("info", "Parsee whitelist was removed."); SetNote("info", "Parsee whitelist was removed.");
(void) form; (void) form;
(void) node;
} }
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -79,9 +80,10 @@ AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLEle
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
SetNote("info", "Server successfully whitelisted."); SetNote("info", "Server successfully whitelisted.");
(void) node;
} }
void 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); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
@ -139,4 +141,5 @@ WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen
} }
(void) form; (void) form;
(void) node;
} }

View file

@ -3,7 +3,6 @@
#include <Cytoplasm/Util.h> #include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h> #include <Cytoplasm/Sha.h>
#include <Cytoplasm/Log.h>
#include <StringStream.h> #include <StringStream.h>
#include <XMPPCommand.h> #include <XMPPCommand.h>
@ -13,86 +12,33 @@
#include "XMPPThread/internal.h" #include "XMPPThread/internal.h"
IQFeatures *
CreateIQFeatures(void)
{
IQFeatures *ret = Malloc(sizeof(*ret));
ret->identity = ArrayCreate();
ret->adverts = ArrayCreate();
return ret;
}
void
FreeIQFeatures(IQFeatures *features)
{
size_t i;
if (!features)
{
return;
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
Free(ArrayGet(features->adverts, i));
}
ArrayFree(features->adverts);
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
Free(identity->category);
Free(identity->type);
Free(identity->lang);
Free(identity->name);
Free(identity);
}
ArrayFree(features->identity);
Free(features);
}
void
AdvertiseIQFeature(IQFeatures *f, char *feature)
{
if (!f || !feature)
{
return;
}
ArrayAdd(f->adverts, StrDuplicate(feature));
}
void
AddIQIdentity(IQFeatures *f, char *cat, char *lang, char *type, char *name)
{
XMPPIdentity *identity;
if (!f)
{
return;
}
identity = Malloc(sizeof(*identity));
identity->category = StrDuplicate(cat);
identity->type = StrDuplicate(type);
identity->lang = StrDuplicate(lang);
identity->name = StrDuplicate(name);
ArrayAdd(f->identity, identity);
}
/* Generates a SHA-256 hash of the ver field. */ /* Generates a SHA-256 hash of the ver field. */
char * char *
XMPPGenerateVer(IQFeatures *features) XMPPGenerateVer(void)
{ {
char *S = NULL; char *S = NULL;
unsigned char *Sha = NULL; unsigned char *Sha = NULL;
Array *identities = ArrayCreate();
Array *features = ArrayCreate();
size_t i; size_t i;
/* Initialise identity table, to be sorted */ /* Initialise identity table, to be sorted */
ArraySort(features->identity, IdentitySort); #define IdentitySimple(cat, Type, Name) { \
for (i = 0; i < ArraySize(features->identity); i++) XMPPIdentity *id = Malloc(sizeof(*id)); \
id->category = cat; \
id->lang = NULL; \
id->type = Type; \
id->name = Name; \
ArrayAdd(identities, id); }
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) ArrayAdd(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
ArraySort(identities, IdentitySort);
for (i = 0; i < ArraySize(identities); i++)
{ {
XMPPIdentity *identity = ArrayGet(features->identity, i); XMPPIdentity *identity = ArrayGet(identities, i);
char *id_chunk = StrConcat(7, char *id_chunk = StrConcat(7,
identity->category, "/", identity->category, "/",
identity->type, "/", identity->type, "/",
@ -104,10 +50,10 @@ XMPPGenerateVer(IQFeatures *features)
Free(id_chunk); Free(id_chunk);
} }
ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate)); ArraySort(features, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features->adverts); i++) for (i = 0; i < ArraySize(features); i++)
{ {
char *feature = ArrayGet(features->adverts, i); char *feature = ArrayGet(features, i);
char *tmp = S; char *tmp = S;
S = StrConcat(3, S, feature, "<"); S = StrConcat(3, S, feature, "<");
Free(tmp); Free(tmp);
@ -118,64 +64,16 @@ XMPPGenerateVer(IQFeatures *features)
S = Base64Encode((const char *) Sha, 20); S = Base64Encode((const char *) Sha, 20);
Free(Sha); Free(Sha);
ArrayFree(features);
for (i = 0; i < ArraySize(identities); i++)
{
XMPPIdentity *identity = ArrayGet(identities, i);
/* We don't have to do anything here. */
Free(identity);
}
ArrayFree(identities);
return S; return S;
} }
void
XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features)
{
XMLElement *c;
char *ver;
if (!presence || !features)
{
return;
}
ver = XMPPGenerateVer(features);
c = XMLCreateTag("c");
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
XMLAddAttr(c, "hash", "sha-1");
XMLAddAttr(c, "node", REPOSITORY);
XMLAddAttr(c, "ver", ver);
Free(ver);
XMLAddChild(presence, c);
}
IQFeatures *
LookupJIDFeatures(char *jid)
{
IQFeatures *features;
if (!jid)
{
return NULL;
}
features = CreateIQFeatures();
if (*jid == '#')
{
/* This is a MUC. As such, we need to advertise MUCs */
#define ID(...) AddIQIdentity(features, __VA_ARGS__)
#define AD(var) AdvertiseIQFeature(features, var)
ID("gateway", NULL, "matrix", "Parsee MUC gateway");
ID("conference", NULL, "text", "Parsee MUC gateway");
ID("component", NULL, "generic", "Parsee component");
AD("http://jabber.org/protocol/muc");
#undef AD
#undef ID
}
else
{
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
}
return features;
}

View file

@ -94,15 +94,13 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item)
AddTextField(vcard, "fn", name); AddTextField(vcard, "fn", name);
AddTextField(vcard, "nickname", mxid); AddTextField(vcard, "nickname", mxid);
AddURIField(vcard, "url", REPOSITORY);
AddURIField(vcard, "url", "https://kappach.at/parsee"); AddURIField(vcard, "url", "https://kappach.at/parsee");
AddURIField(vcard, "url", "https://matrix.org");
AddURIField(vcard, "url", m_to); AddURIField(vcard, "url", m_to);
AddTextField( AddTextField(
vcard, vcard,
"note", "note",
"This is a bridged Matrix user, from Parsee." "This is a bridged Matrix user, from " NAME "."
); );
Free(mxid); Free(mxid);
Free(name); Free(name);

View file

@ -56,8 +56,6 @@ XMPPDispatcher(void *argp)
if (!stanza) if (!stanza)
{ {
/* TODO: We shouldn't be busywaiting. Even with a sleep call.
*/
UtilSleepMillis(10); UtilSleepMillis(10);
continue; continue;
} }
@ -67,8 +65,12 @@ XMPPDispatcher(void *argp)
Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name);
} }
if (ManageMUCStanza(args->muc, stanza)) if (ExtensionPushStanza(args->exts, stanza, STANZA_RAW))
{ {
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Stanza was vetoed by an extension.");
}
XMLFreeElement(stanza); XMLFreeElement(stanza);
continue; continue;
} }
@ -138,6 +140,7 @@ bool
XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza) XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
{ {
ParseeData *args = XMPPGetManagerCookie(m); ParseeData *args = XMPPGetManagerCookie(m);
XMPPCommandFlags flags;
char *trimmed_from; char *trimmed_from;
char *from; char *from;
char *chat_id; char *chat_id;
@ -152,28 +155,24 @@ XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from)); is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from));
Free(trimmed_from); Free(trimmed_from);
Free(chat_id); Free(chat_id);
#define XMPP_COMMAND(f,l,n,t,s) \ flags = XMPPGetCommandFlags(m, id);
if (StrEquals(n, id)) \
{ \ if (flags == XMPPCMD_ALL)
if (l == XMPPCMD_ALL) \ {
{ \ return true;
return true; \ }
} \ else if (flags == XMPPCMD_MUC)
else if (l == XMPPCMD_MUC) \ {
{ \ return is_muc;
return is_muc; \ }
} \ else if (flags == XMPPCMD_ADMINS)
else if (l == XMPPCMD_ADMINS) \ {
{ \ bool is_admin;
bool is_admin; \ trimmed_from = ParseeTrimJID(from);
trimmed_from = ParseeTrimJID(from); \ is_admin = ParseeIsAdmin(args, trimmed_from);
is_admin = ParseeIsAdmin(args, trimmed_from); \ Free(trimmed_from);
Free(trimmed_from); \ return is_admin;
return is_admin; \
} \
} }
XMPPCOMMANDS
#undef XMPP_COMMAND
return false; return false;
} }
@ -199,13 +198,14 @@ ParseeXMPPThread(void *argp)
XMPPCommand *cmd; XMPPCommand *cmd;
#define XMPP_COMMAND(f,l,n,t,s) \ #define XMPP_COMMAND(f,l,n,t,s) \
cmd = XMPPBasicCmd( \ cmd = XMPPBasicCmd( \
n, t, f \ l, n, t, f \
); \ ); \
s \ s \
XMPPRegisterCommand(info.m, cmd); XMPPRegisterCommand(info.m, cmd);
XMPPCOMMANDS XMPPCOMMANDS
#undef XMPP_COMMAND #undef XMPP_COMMAND
} }
ExtensionRequestCommands(args->exts);
info.pep_manager = CreatePEPManager(args, &info); info.pep_manager = CreatePEPManager(args, &info);
{ {
PEPManagerAddEvent( PEPManagerAddEvent(
@ -244,8 +244,6 @@ ParseeXMPPThread(void *argp)
} }
} }
while (!args->halted)
{
while (true) while (true)
{ {
char *id; char *id;
@ -302,38 +300,10 @@ ParseeXMPPThread(void *argp)
if (!args->halted) if (!args->halted)
{ {
Log(LOG_WARNING, "XMPP server is closing stream..."); Log(LOG_WARNING, "XMPP server is closing stream...");
for (size_t i = 0; i < 50; i++) Log(LOG_WARNING, "Stopping %s...", NAME);
{ error = true;
UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */
Log(LOG_WARNING, "Restarting XMPP stream.");
/* This is the part where a new connection is being considered */
XMPPFinishCompStream(jabber);
XMPPEndCompStream(jabber);
args->jabber = XMPPInitialiseCompStream(
args->config->component_addr,
args->config->component_host,
args->config->component_port
);
jabber = args->jabber;
if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret))
{
/* Oops, there is something wrong! */
Log(LOG_ERR, "Couldn't authenticate to XMPP server");
XMPPEndCompStream(jabber);
args->jabber = NULL;
jabber = NULL;
if (i == 4)
{
pthread_mutex_unlock(&args->halt_lock);
break;
}
}
}
} }
pthread_mutex_unlock(&args->halt_lock); pthread_mutex_unlock(&args->halt_lock);
}
info.running = false; info.running = false;
for (i = 0; i < info.available_dispatchers; i++) for (i = 0; i < info.available_dispatchers; i++)

View file

@ -9,7 +9,6 @@
#include <Matrix.h> #include <Matrix.h>
#include <AS.h> #include <AS.h>
#include <sys/utsname.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -116,37 +115,33 @@ end:
#define DISCO "http://jabber.org/protocol/disco#info" #define DISCO "http://jabber.org/protocol/disco#info"
static XMLElement * static XMLElement *
IQGenerateQuery(IQFeatures *features) IQGenerateQuery(void)
{ {
XMLElement *query; XMLElement *query = XMLCreateTag("query");
if (!features)
{
return NULL;
}
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO); XMLAddAttr(query, "xmlns", DISCO);
{ {
XMLElement *feature; XMLElement *feature;
size_t i; #define IdentitySimple(c,t,n) do \
for (i = 0; i < ArraySize(features->identity); i++) { \
{ feature = XMLCreateTag("identity"); \
XMPPIdentity *identity = ArrayGet(features->identity, i); XMLAddAttr(feature, "category", c); \
XMLAddAttr(feature, "type", t); \
XMLAddAttr(feature, "name", n); \
XMLAddChild(query, feature); \
} \
while (0);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(f) do \
{ \
feature = XMLCreateTag("feature"); \
XMLAddAttr(feature, "var", f); \
XMLAddChild(query, feature); \
} \
while (0);
feature = XMLCreateTag("identity"); IQ_ADVERT
XMLAddAttr(feature, "category", identity->category); #undef AdvertiseSimple
XMLAddAttr(feature, "type", identity->type);
XMLAddAttr(feature, "name", identity->name);
XMLAddChild(query, feature);
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
char *var = ArrayGet(features->adverts, i);
feature = XMLCreateTag("feature");
XMLAddAttr(feature, "var", var);
XMLAddChild(query, feature);
}
} }
return query; return query;
@ -156,7 +151,6 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
{ {
char *from, *to, *id; char *from, *to, *id;
XMLElement *iq_reply, *query; XMLElement *iq_reply, *query;
IQFeatures *features;
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to"); to = HashMapGet(stanza->attrs, "to");
@ -169,10 +163,9 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "id", id); XMLAddAttr(iq_reply, "id", id);
features = LookupJIDFeatures(to); query = IQGenerateQuery();
query = IQGenerateQuery(features);
{ {
char *ver = XMPPGenerateVer(features); char *ver = XMPPGenerateVer();
char *node = StrConcat(3, REPOSITORY, "#", ver); char *node = StrConcat(3, REPOSITORY, "#", ver);
XMLAddAttr(query, "node", node); XMLAddAttr(query, "node", node);
@ -180,7 +173,6 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
Free(ver); Free(ver);
} }
XMLAddChild(iq_reply, query); XMLAddChild(iq_reply, query);
FreeIQFeatures(features);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLFreeElement(iq_reply); XMLFreeElement(iq_reply);
@ -374,7 +366,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
bool bool
IQIsCommandList(ParseeData *args, XMLElement *stanza) IQIsCommandList(ParseeData *args, XMLElement *stanza)
{ {
char *parsee = NULL, *to; char *parsee = NULL;
XMLElement *query = XMLookForTKV( XMLElement *query = XMLookForTKV(
stanza, "query", "xmlns", stanza, "query", "xmlns",
"http://jabber.org/protocol/disco#items" "http://jabber.org/protocol/disco#items"
@ -389,8 +381,7 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza)
} }
parsee = ParseeJID(args); parsee = ParseeJID(args);
to = HashMapGet(stanza->attrs, "to"); ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee);
ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host);
Free(parsee); Free(parsee);
return ret; return ret;
@ -430,10 +421,16 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee"); char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee");
XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items");
XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands");
XMPPShoveCommandList(thr->info->m, XMPPShoveCommandList(thr->info->m,
IsInMUC(args, from) ? parsee_muc_jid : to, IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza q, stanza
); );
ExtensionShoveCommands(
args->exts,
IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza
);
XMLAddChild(iq_reply, q); XMLAddChild(iq_reply, q);
Free(parsee_muc_jid); Free(parsee_muc_jid);
} }
@ -574,7 +571,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version"))
{ {
XMLElement *iq_reply, *query; XMLElement *iq_reply, *query;
XMLElement *name, *version, *os; XMLElement *name, *version;
iq_reply = XMLCreateTag("iq"); iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "to", from);
@ -585,19 +582,14 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
query = XMLCreateTag("query"); query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "jabber:iq:version"); XMLAddAttr(query, "xmlns", "jabber:iq:version");
{ {
struct utsname info;
name = XMLCreateTag("name"); name = XMLCreateTag("name");
version = XMLCreateTag("version"); version = XMLCreateTag("version");
os = XMLCreateTag("os");
uname(&info);
XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(name, XMLCreateText(NAME));
XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]"));
XMLAddChild(os, XMLCreateText(info.sysname));
} }
XMLAddChild(query, name); XMLAddChild(query, name);
XMLAddChild(query, version); XMLAddChild(query, version);
XMLAddChild(query, os);
XMLAddChild(iq_reply, query); XMLAddChild(iq_reply, query);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
@ -605,14 +597,10 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
} }
else else
{ {
char *buf = NULL;
Stream *s = StrStreamWriter(&buf);
Log(LOG_WARNING, "Unknown I/Q received:"); Log(LOG_WARNING, "Unknown I/Q received:");
XMLEncode(s, stanza); XMLEncode(StreamStdout(), stanza);
StreamFlush(s); StreamPrintf(StreamStdout(),"\n");
StreamClose(s); StreamFlush(StreamStdout());
Log(LOG_WARNING, "%s", buf);
Free(buf);
} }
} }
@ -629,6 +617,7 @@ IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
XMPPCommandManager *manager = thr->info->m; XMPPCommandManager *manager = thr->info->m;
XMPPManageCommand(manager, stanza, args); XMPPManageCommand(manager, stanza, args);
ExtensionManageCommands(args->exts, stanza);
} }
#undef DISCO #undef DISCO

View file

@ -5,7 +5,6 @@
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Unistring.h>
#include <Matrix.h> #include <Matrix.h>
#include <AS.h> #include <AS.h>
@ -277,7 +276,7 @@ end_error:
Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime)); Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime));
} }
if (moderated && !(!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))) if (moderated)
{ {
/* TODO: Parsee MUST check if it is a valid MUC */ /* TODO: Parsee MUST check if it is a valid MUC */
char *resource = ParseeGetResource(from); char *resource = ParseeGetResource(from);
@ -325,9 +324,8 @@ end_error:
{ {
Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime)); Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime));
} }
char *trim = ParseeTrimJID(from);
if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) &&
to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL)) to && *to == '@')
{ {
DbRef *room_ref; DbRef *room_ref;
HashMap *room_json; HashMap *room_json;
@ -346,7 +344,6 @@ end_error:
ParseePushDMRoom(args, to, from, room); ParseePushDMRoom(args, to, from, room);
} }
} }
Free(trim);
/* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER /* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER
* SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED * SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED
@ -397,16 +394,10 @@ end_error:
bool media_enabled = chat_id ? bool media_enabled = chat_id ?
ParseeIsMediaEnabled(args, chat_id) : ParseeIsMediaEnabled(args, chat_id) :
true ; true ;
bool is_parsee;
/* Note that chat_id still has meaningful info as a boolean
* despite being freed */
Free(chat_id);
pthread_mutex_unlock(&thr->info->chk_lock);
parsee = ParseeJID(args); Free(chat_id);
is_parsee = StrEquals(to, parsee);
Free(parsee); pthread_mutex_unlock(&thr->info->chk_lock);
parsee = NULL;
LazyRegister(args, encoded, !chat ? res : NULL); LazyRegister(args, encoded, !chat ? res : NULL);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
@ -420,10 +411,15 @@ end_error:
/* Check if it is a media link */ /* Check if it is a media link */
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
if (chat_id && StrEquals(type, "chat") && !is_parsee)
/* 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))
{ {
/* Very clearly a MUC DM. */ //if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
event_id = NULL; {
Log(LOG_DEBUG, "Stanza was vetoed by an extension.");
}
} }
else if (oob && data && media_enabled) else if (oob && data && media_enabled)
{ {
@ -521,15 +517,6 @@ end_error:
* too. Go figure. */ * too. Go figure. */
size_t off = size_t off =
reply_to ? ParseeFindDatastart(data->data) : 0; reply_to ? ParseeFindDatastart(data->data) : 0;
size_t stanzaoff = XMPPGetReplyOffset(stanza);
if (reply_to && stanzaoff != -1)
{
off = UnistrGetUTFOffset(data->data, stanzaoff);
}
if (data->data && off >= strlen(data->data))
{
off = 0;
}
HashMap *ev = MatrixCreateMessage(data->data + off); HashMap *ev = MatrixCreateMessage(data->data + off);
if (reply_to) if (reply_to)
{ {

View file

@ -298,7 +298,7 @@ end_item:
Free(room); Free(room);
FreeStatuses(statuses); FreeStatuses(statuses);
} }
if (status && !StrEquals(type, "unavailable")) if (status)
{ {
XMLElement *status_data = ArrayGet(status->children, 0); XMLElement *status_data = ArrayGet(status->children, 0);
char *decode_from = ParseeLookupJID(oid); char *decode_from = ParseeLookupJID(oid);
@ -309,8 +309,6 @@ end_item:
status_str = status_data->data; status_str = status_data->data;
} }
/* TODO: "The server will automatically set a user's presence to /* TODO: "The server will automatically set a user's presence to
* unavailable if their last active time was over a threshold value * unavailable if their last active time was over a threshold value
* (e.g. 5 minutes)." * (e.g. 5 minutes)."

View file

@ -32,17 +32,13 @@
IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \
IdentitySimple("component", "generic", "Parsee's component") IdentitySimple("component", "generic", "Parsee's component")
typedef struct IQFeatures { typedef struct PEPManager PEPManager;
Array *identity; typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
Array *adverts;
} IQFeatures;
typedef struct XMPPIdentity { typedef struct XMPPIdentity {
char *category, *type, *lang, *name; char *category, *type, *lang, *name;
} XMPPIdentity; } XMPPIdentity;
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct XMPPThread XMPPThread; typedef struct XMPPThread XMPPThread;
typedef struct XMPPThreadInfo { typedef struct XMPPThreadInfo {
/* A FIFO of stanzas inbound, to be read by dispatcher /* A FIFO of stanzas inbound, to be read by dispatcher
@ -69,33 +65,9 @@ struct XMPPThread {
int ICollate(unsigned char *cata, unsigned char *catb); int ICollate(unsigned char *cata, unsigned char *catb);
int IdentitySort(void *idap, void *idbp); int IdentitySort(void *idap, void *idbp);
IQFeatures * CreateIQFeatures(void);
void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *name);
void AdvertiseIQFeature(IQFeatures *, char *feature);
void FreeIQFeatures(IQFeatures *);
/**
* Looks up the features supported by a JID
* ----------------------
* Returns: an IQ featureset[LA:HEAP] | NULL
* Thrasher: FreeIQFeatures
* Modifies: NOTHING */
IQFeatures * LookupJIDFeatures(char *jid);
/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps.
* --------
* Returns: A base64 encoded ver hash[LA:HEAP]
* Modifies: NOTHING
* See-Also: https://xmpp.org/extensions/xep-0115.html */
char * XMPPGenerateVer(IQFeatures *features);
/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */
void XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features);
char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza); char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza);
char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id); char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id);
char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza); char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza);
void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event);
bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza); bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza);
HashMap * ShoveStanza(HashMap *content, XMLElement *stanza); HashMap * ShoveStanza(HashMap *content, XMLElement *stanza);
@ -109,9 +81,6 @@ void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
bool ServerHasXEP421(ParseeData *data, char *from); 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); PEPManager * CreatePEPManager(ParseeData *data, void *cookie);
void * PEPManagerCookie(PEPManager *manager); void * PEPManagerCookie(PEPManager *manager);
void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event); void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event);

81
src/include/Extension.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef PARSEE_EXTENSION_H
#define PARSEE_EXTENSION_H
#include <stdbool.h>
#include <Cytoplasm/Json.h>
#include <XML.h>
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 <strong>WILL</strong> 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

View file

@ -1,39 +0,0 @@
#ifndef PARSEE_MUCSERV_H
#define PARSEE_MUCSERV_H
/*-* An easy interface for handling MUCs.
*/
typedef struct MUCServer MUCServer;
#include <Parsee.h>
#include <stdbool.h>
/** Creates a MUC server handle given a ParseeData handle.
* This handle shall be used for all MUC-related operations.
* ----------------------------------------------------------
* Returns: a MUC server handle[HEAP] | NULL
* Thrasher: FreeMUCServer
* See-Also: https://xmpp.org/extensions/xep-0045.html */
extern MUCServer * CreateMUCServer(ParseeData *data);
/** Manages a stanza from a MUC, and returns true if the stanza
* was actually processed by the MUC server.
* -------------------------------------------------------------
* See-Also: CreateMUCServer */
extern bool ManageMUCStanza(MUCServer *serv, XMLElement *stanza);
/** Checks if a MUC(from its localpart, so
* <code>'#foobar@bridge.blow.hole' -> 'foobar'</code>) exists.
* --------------
* Returns: whenever the MUC exists */
extern bool MUCServerExists(MUCServer *serv, char *muc);
/** Destroys all memory and references from a MUC server handle.
* --------------
* Thrashes: CreateMUCServer
* See-Also: https://xmpp.org/extensions/xep-0045.html */
extern void FreeMUCServer(MUCServer *serv);
#endif

View file

@ -2,7 +2,6 @@
#define PARSEE_MATRIX_H #define PARSEE_MATRIX_H
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Uri.h>
#include "FileInfo.h" #include "FileInfo.h"
@ -21,12 +20,6 @@ typedef struct UserID {
* Thrasher: Free */ * Thrasher: Free */
extern UserID * MatrixParseID(char *user); extern UserID * MatrixParseID(char *user);
/** Attempts to parse a Matrix ID from a matrix.to URL.
* -------------------------------------------------
* Returns: a valid user ID[HEAP] | NULL
* Thrasher: Free */
extern UserID * MatrixParseIDFromMTO(Uri *uri);
/* Creates an error message JSON, with the specified code and message. */ /* Creates an error message JSON, with the specified code and message. */
extern HashMap * MatrixCreateError(char *err, char *msg); extern HashMap * MatrixCreateError(char *err, char *msg);

View file

@ -15,11 +15,10 @@ typedef struct ParseeData ParseeData;
#include <pthread.h> #include <pthread.h>
#include <Extension.h>
#include <Command.h> #include <Command.h>
#include <XMPP.h> #include <XMPP.h>
#include <MUCServ.h>
#define PARSEE_VERBOSE_NONE 0 #define PARSEE_VERBOSE_NONE 0
#define PARSEE_VERBOSE_LOG 1 #define PARSEE_VERBOSE_LOG 1
#define PARSEE_VERBOSE_TIMINGS 2 #define PARSEE_VERBOSE_TIMINGS 2
@ -41,14 +40,9 @@ typedef struct ParseeConfig {
/* Homeserver port info */ /* Homeserver port info */
char *homeserver_host; char *homeserver_host;
int homeserver_port; int homeserver_port;
int homeserver_tls;
bool accept_pings;
bool ignore_bots;
/* ------- JABBER -------- */ /* ------- JABBER -------- */
char *component_addr;
char *component_host; char *component_host;
char *shared_comp_secret; char *shared_comp_secret;
int component_port; int component_port;
@ -57,6 +51,7 @@ typedef struct ParseeConfig {
/* ------- DB -------- */ /* ------- DB -------- */
char *db_path; char *db_path;
size_t db_size; size_t db_size;
char *extensions;
/* - COMMAND-LINE FLAGS - */ /* - COMMAND-LINE FLAGS - */
int xmpp_threads, http_threads; int xmpp_threads, http_threads;
@ -69,7 +64,6 @@ typedef struct ParseeData {
HttpServer *server; HttpServer *server;
XMPPComponent *jabber; XMPPComponent *jabber;
MUCServer *muc;
Db *db; Db *db;
char *id; char *id;
@ -80,8 +74,10 @@ typedef struct ParseeData {
pthread_mutex_t oidl; pthread_mutex_t oidl;
/* If Parsee was intentionally halted */ /* If Parsee was intentionally halted */
bool halted; volatile bool halted;
pthread_mutex_t halt_lock; pthread_mutex_t halt_lock;
Extensions *exts;
} ParseeData; } ParseeData;
typedef struct Argument { typedef struct Argument {
@ -101,6 +97,8 @@ typedef struct Argument {
#define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__)) #define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__))
#define GrabArray(obj, ...) JsonValueAsArray(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 /* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTES
* in timestamps */ * in timestamps */
#define SECONDS * 1000 #define SECONDS * 1000
@ -129,6 +127,7 @@ extern const char *parsee_ascii[PARSEE_ASCII_LINES];
* Modifies: the logger output */ * Modifies: the logger output */
extern void ParseePrintASCII(void); extern void ParseePrintASCII(void);
/** /**
* Checks if two versions of Parsee can be considered "compatible". * Checks if two versions of Parsee can be considered "compatible".
* This is mainly used for things like database operations. TODO: * This is mainly used for things like database operations. TODO:
@ -284,6 +283,12 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id);
/* Finds the MUC JID from a chat ID */ /* Finds the MUC JID from a chat ID */
extern char * ParseeGetMUCID(ParseeData *, char *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), /** 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). * as a string or NULL. Keys are to be stored like Java packages(reveres DNS).
* Parsee has the right over any key with the <code>'p.'</code> prefix. * Parsee has the right over any key with the <code>'p.'</code> prefix.
@ -329,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 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); 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 */ /* Checks if a stanza is not duplicated in a chat ID */
extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id); extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id);
@ -359,7 +372,7 @@ extern int ParseeFindDatastartU(char *data);
/* XMPP-ifies a message event to XEP-0393 if possible. */ /* XMPP-ifies a message event to XEP-0393 if possible. */
extern char * ParseeXMPPify(ParseeData *data, HashMap *event); extern char * ParseeXMPPify(HashMap *event);
/* Finds an event ID from an ID in the stanza's attributes */ /* Finds an event ID from an ID in the stanza's attributes */
extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id); extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id);
@ -369,7 +382,7 @@ extern char * ParseeDMEventFromID(ParseeData *d, char *r_id, char *ori_id);
extern char * ParseeDMEventFromSID(ParseeData *d, char *r_id, char *ori_id); extern char * ParseeDMEventFromSID(ParseeData *d, char *r_id, char *ori_id);
/* Gets a Parsee "shim" link to an MXC, usable as unauth for a limited time */ /* Gets a Parsee "shim" link to an MXC, usable as unauth for a limited time */
extern char * ParseeToUnauth(ParseeData *data, char *mxc, char *filename); extern char * ParseeToUnauth(ParseeData *data, char *mxc);
extern void ParseeInitialiseOIDTable(void); extern void ParseeInitialiseOIDTable(void);
extern void ParseePushOIDTable(char *muc, char *occupant); extern void ParseePushOIDTable(char *muc, char *occupant);
@ -400,17 +413,10 @@ extern void ParseeDestroyNickTable(void);
* to ban them from rooms where Parsee has the ability to do so ("noflying"). * to ban them from rooms where Parsee has the ability to do so ("noflying").
* --------------- * ---------------
* Returns: NOTHING * Returns: NOTHING
* See-Also: ParseeManageBan, ParseeGlobalUnban * See-Also: ParseeManageBan
* Modifies: the database */ * Modifies: the database */
extern void ParseeGlobalBan(ParseeData *, char *user, char *reason); extern void ParseeGlobalBan(ParseeData *, char *user, char *reason);
/** Revokes a user/room/MUC's nofly status
* ---------------
* Returns: NOTHING
* See-Also: ParseeManageBan, ParseeGlobalBan
* Modifies: the database */
extern void ParseeGlobalUnban(ParseeData *, char *user);
/** Verifies if a user was banned globally. If so (and if {room} is set), /** Verifies if a user was banned globally. If so (and if {room} is set),
* tries to ban the user from it. * tries to ban the user from it.
* --------------- * ---------------
@ -503,4 +509,8 @@ extern void ParseeUnlinkRoom(ParseeData *data, char *chat_id);
* Modifies: NOTHING */ * Modifies: NOTHING */
extern bool ParseeIsMUCWhitelisted(ParseeData *data, char *muc); 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 #endif

View file

@ -27,10 +27,6 @@ typedef struct ParseeCmdArg {
"ban-list", CmdNoFlyList, \ "ban-list", CmdNoFlyList, \
"Globally bans a user from using Parsee" \ "Globally bans a user from using Parsee" \
) \ ) \
X_COMMAND( \
"unban-list", CmdNoFlyListDel, \
"Globally unbans a user from using Parsee" \
) \
X_COMMAND( \ X_COMMAND( \
"nuke-muc", CmdUnlinkMUC, \ "nuke-muc", CmdUnlinkMUC, \
"Removes a MUC. Users should then run " \ "Removes a MUC. Users should then run " \
@ -79,8 +75,7 @@ typedef struct ParseeCmdArg {
X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \ X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \
X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \ X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \
X_ROUTE("/_matrix/app/v1/ping", RoutePing) \ X_ROUTE("/_matrix/app/v1/ping", RoutePing) \
X_ROUTE("/media/(.*)/(.*)", RouteMedia) \ X_ROUTE("/_matrix/client/v1/media/download/(.*)/(.*)", RouteMedia)
X_ROUTE("/media/(.*)/(.*)/(.*)", RouteMedia)
#define X_ROUTE(path, name) extern void * name(Array *, void *); #define X_ROUTE(path, name) extern void * name(Array *, void *);
ROUTES ROUTES

View file

@ -64,12 +64,6 @@ extern void UnistrFree(Unistr *unistr);
* Returns: whenever the character is within the BMP */ * Returns: whenever the character is within the BMP */
extern bool UnistrIsBMP(uint32_t u); extern bool UnistrIsBMP(uint32_t u);
/** Returns true IFF the character is within the 7-bit ASCII range
* not 0x0000
* ------------------------------------------------------------
* Returns: whenever the character is within ASCII */
extern bool UnistrIsASCII(uint32_t u);
typedef bool (*UnistrFilterFunc)(uint32_t u); typedef bool (*UnistrFilterFunc)(uint32_t u);
/** "Filters" characters in a Unistring by codepoint, removing /** "Filters" characters in a Unistring by codepoint, removing
* those with callbacks which return false into a new unistring. * those with callbacks which return false into a new unistring.
@ -82,10 +76,4 @@ extern Unistr * UnistrFilter(Unistr *str, UnistrFilterFunc filter);
* -------- * --------
* Returns: an offset of the first line to not start by {c} | 0 */ * Returns: an offset of the first line to not start by {c} | 0 */
extern size_t UnistrGetOffset(Unistr *str, uint32_t sep); extern size_t UnistrGetOffset(Unistr *str, uint32_t sep);
/** Locates the new index within a regular UTF-8 sequence from an index
* within all codepoints.
* -----------------------------------
* Returns: an offset */
extern size_t UnistrGetUTFOffset(char *cstr, size_t unicode);
#endif #endif

View file

@ -14,8 +14,6 @@ typedef struct XMLEvent {
XML_LEXER_ELEM, /* Empty attr */ XML_LEXER_ELEM, /* Empty attr */
XML_LEXER_ENDELEM, XML_LEXER_ENDELEM,
XML_ERROR,
XML_RELAX XML_RELAX
} type; } type;

View file

@ -22,7 +22,7 @@ typedef struct XMPPComponent {
/* Initialises a raw component stream to host, with an optional port. /* Initialises a raw component stream to host, with an optional port.
* If said port is 0, then it is set to the default Prosody port */ * If said port is 0, then it is set to the default Prosody port */
extern XMPPComponent * XMPPInitialiseCompStream(char *addr, char *host, int port); extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port);
/* Authenticates a component stream with a given shared secret, /* Authenticates a component stream with a given shared secret,
* with a stream ID from the server. This should be called right * with a stream ID from the server. This should be called right
@ -90,9 +90,6 @@ extern char * XMPPGetRetractedID(XMLElement *);
/* Get the replied-to stanza ID, if existent. */ /* Get the replied-to stanza ID, if existent. */
extern char * XMPPGetReply(XMLElement *elem); extern char * XMPPGetReply(XMLElement *elem);
/* Get the replied-to stanza offset, if existent. */
extern ssize_t XMPPGetReplyOffset(XMLElement *elem);
/** Get the moderated message ID(as a stanza ID/plain ID) from a moderation /** Get the moderated message ID(as a stanza ID/plain ID) from a moderation
* stanza, that lives *alongside* the stanza itself. * stanza, that lives *alongside* the stanza itself.
* ---------------------------------------------------------------------- * ----------------------------------------------------------------------
@ -101,6 +98,16 @@ extern ssize_t XMPPGetReplyOffset(XMLElement *elem);
* See-Also: https://xmpp.org/extensions/xep-0425.html */ * See-Also: https://xmpp.org/extensions/xep-0425.html */
extern char * XMPPGetModeration(XMLElement *stanza); extern char * XMPPGetModeration(XMLElement *stanza);
/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps.
* --------
* Returns: A base64 encoded ver hash[LA:HEAP]
* Modifies: NOTHING
* See-Also: https://xmpp.org/extensions/xep-0115.html */
extern char * XMPPGenerateVer(void);
/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */
extern void XMPPAnnotatePresence(XMLElement *presence);
extern bool XMPPHasError(XMLElement *stanza, char *type); extern bool XMPPHasError(XMLElement *stanza, char *type);
#include <Parsee.h> #include <Parsee.h>

View file

@ -1,3 +1,4 @@
typedef struct XMPPCommandManager XMPPCommandManager;
#ifndef PARSEE_XMPPCOMMAND_H #ifndef PARSEE_XMPPCOMMAND_H
#define PARSEE_XMPPCOMMAND_H #define PARSEE_XMPPCOMMAND_H
@ -7,11 +8,16 @@
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
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 XMPPCommand XMPPCommand;
typedef struct XMPPOption XMPPOption; typedef struct XMPPOption XMPPOption;
typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *); typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *);
typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *); typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *, char *);
typedef bool (*XMPPCmdFilter)(XMPPCommandManager *, char *id, XMLElement *stanza); typedef bool (*XMPPCmdFilter)(XMPPCommandManager *, char *id, XMLElement *stanza);
/** Creates a simple XMPP command manager, which routes commands /** Creates a simple XMPP command manager, which routes commands
@ -39,7 +45,7 @@ XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter);
* Modifies: NOTHING * Modifies: NOTHING
* See-Also: XMPPRegisterCommand */ * See-Also: XMPPRegisterCommand */
extern XMPPCommand * extern XMPPCommand *
XMPPBasicCmd(char *node, char *name, XMPPCmdCallback cb); XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback cb);
extern void extern void
XMPPCmdOptionsCreator(XMPPCommand *cmd, XMPPOptionWriter writer); XMPPCmdOptionsCreator(XMPPCommand *cmd, XMPPOptionWriter writer);
extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt); 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 XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction);
extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title); extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title);
extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); 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. /** Create a basic option.
* ----------------------------------------------------- * -----------------------------------------------------
@ -91,6 +97,12 @@ extern void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd);
* See-Also: XMPPCreateManager */ * See-Also: XMPPCreateManager */
extern void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s); 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}. /** Destroys all memory related to the command {manager}.
* ----------------------------------------------------- * -----------------------------------------------------
* Returns: NOTHING * Returns: NOTHING
@ -111,11 +123,11 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD
#define XMPP_COMMAND(f,l,n,t,s) \ #define XMPP_COMMAND(f,l,n,t,s) \
extern void \ extern void \
f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); \ f(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); \
/* This symbol might not exist. We do, however, not care, as /* This symbol might not exist. We do, however, not care, as
* it is not always done. */ \ * it is not always done. */ \
extern void \ extern void \
Form##f(XMPPCommandManager *, XMPPCommand *, char *); \ Form##f(XMPPCommandManager *, XMPPCommand *, char *, char *); \
#include "XMPPCommands.x.h" #include "XMPPCommands.x.h"

View file

@ -1,9 +1,4 @@
/* C X-macro file */ /* C X-macro file */
typedef enum XMPPCommandFlags {
XMPPCMD_ALL = 0,
XMPPCMD_MUC , /* Only for MUCs */
XMPPCMD_ADMINS /* Only for administrators */
} XMPPCommandFlags;
#define XMPPCOMMANDS \ #define XMPPCOMMANDS \
XMPP_COMMAND(StatusCallback, XMPPCMD_ALL, "stats", "Get Parsee statistics", {}) \ XMPP_COMMAND(StatusCallback, XMPPCMD_ALL, "stats", "Get Parsee statistics", {}) \
XMPP_COMMAND(CleanCallback, XMPPCMD_ADMINS, "clean", "Cleanup temporary Parsee data", {}) \ XMPP_COMMAND(CleanCallback, XMPPCMD_ADMINS, "clean", "Cleanup temporary Parsee data", {}) \

View file

@ -89,12 +89,8 @@ Main(Array *args, HashMap *env)
glob = ArrayGet(args, 2); glob = ArrayGet(args, 2);
parsee = GetDB(db_path); parsee = GetDB(db_path);
if (!parsee) if (parsee)
{ {
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
}
if (glob) if (glob)
{ {
@ -115,4 +111,9 @@ Main(Array *args, HashMap *env)
DbClose(parsee); DbClose(parsee);
return EXIT_SUCCESS; return EXIT_SUCCESS;
}
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
} }

View file

@ -108,7 +108,7 @@ GetDB(char *config)
{ {
ret = DbOpenLMDB(db_path, lmdb_size); ret = DbOpenLMDB(db_path, lmdb_size);
} }
else if (!ret)
{ {
ret = DbOpen(db_path, 0); ret = DbOpen(db_path, 0);
} }

View file

@ -20,7 +20,6 @@ Main(Array *args, HashMap *env)
Uri *api_base; Uri *api_base;
char *homeserver = NULL, *jcp = NULL, *jabber = NULL; char *homeserver = NULL, *jcp = NULL, *jabber = NULL;
char *data = NULL, *media = NULL, *listen = NULL; char *data = NULL, *media = NULL, *listen = NULL;
char *component_as = NULL;
int flag, code = EXIT_FAILURE; int flag, code = EXIT_FAILURE;
int port = 5347; int port = 5347;
size_t lmdb_size = 0; size_t lmdb_size = 0;
@ -29,7 +28,7 @@ Main(Array *args, HashMap *env)
listen = "localhost"; listen = "localhost";
ArgParseStateInit(&state); ArgParseStateInit(&state);
while ((flag = ArgParse(&state, args, "H:J:j:s:d:p:m:l:S:M:")) != -1) while ((flag = ArgParse(&state, args, "H:J:s:d:p:m:l:S:M:")) != -1)
{ {
switch (flag) switch (flag)
{ {
@ -46,9 +45,6 @@ Main(Array *args, HashMap *env)
case 'J': case 'J':
jabber = state.optArg; jabber = state.optArg;
break; break;
case 'j':
component_as = state.optArg;
break;
case 'd': case 'd':
data = state.optArg; data = state.optArg;
break; break;
@ -127,7 +123,6 @@ Main(Array *args, HashMap *env)
JsonSet(json, JsonValueString(media), 1, "media_base"); JsonSet(json, JsonValueString(media), 1, "media_base");
JsonSet(json, JsonValueString(listen), 1, "listen_as"); JsonSet(json, JsonValueString(listen), 1, "listen_as");
JsonSet(json, JsonValueString(component_as), 1, "component_addr");
JsonEncode(json, file, JSON_PRETTY); JsonEncode(json, file, JSON_PRETTY);
StreamFlush(file); StreamFlush(file);

View file

@ -1,152 +0,0 @@
/* plumb.c - Small utility to manage plumbings from a shutoff instance.
* ============================================================
* TODO: write other commands, and move some code to common.h
*
* Under CC0, as its a rather useful example of a Parsee tool.
* See LICENSE for more information about Parsee's licensing. */
#include "common.h"
#include <string.h>
static void
DeletePlumbID(Db *parsee, char *chat_id)
{
DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id);
DbRef *chats = DbLock(parsee, 1, "chats");
char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id");
char *jabber_id = GrabString(DbJson(chat_id_ref), 1, "jabber_id");
HashMap *rooms = GrabObject(DbJson(chats), 1, "rooms");
HashMap *mucs = GrabObject(DbJson(chats), 1, "mucs");
JsonValueFree(HashMapDelete(rooms, matrix_id));
JsonValueFree(HashMapDelete(mucs, jabber_id));
DbUnlock(parsee, chat_id_ref);
DbUnlock(parsee, chats);
DbDelete(parsee, 2, "chats", chat_id);
}
static void
DeletePlumb(Db *parsee, char *potential_id)
{
if (!parsee || !potential_id)
{
return;
}
if (!strncmp(potential_id, "xmpp:", 5))
{
DbRef *ref;
HashMap *mucs;
char *chat_id;
/* Try to parse it as an XMPP address */
potential_id += 5;
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
mucs = GrabObject(DbJson(ref), 1, "mucs");
chat_id = StrDuplicate(GrabString(mucs, 1, potential_id));
DbUnlock(parsee, ref);
DeletePlumbID(parsee, chat_id);
Free(chat_id);
return;
}
if (*potential_id == '!')
{
/* Try to parse it as a Matrix room ID */
DbRef *ref;
HashMap *rooms;
char *chat_id;
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
rooms = GrabObject(DbJson(ref), 1, "rooms");
chat_id = StrDuplicate(GrabString(rooms, 1, potential_id));
DbUnlock(parsee, ref);
DeletePlumbID(parsee, chat_id);
Free(chat_id);
return;
}
/* Try to parse it as a chat ID */
DeletePlumbID(parsee, potential_id);
}
static void
ListPlumbs(Db *parsee)
{
DbRef *ref;
HashMap *mucs;
char *muc;
JsonValue *value;
if (!parsee)
{
return;
}
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
mucs = GrabObject(DbJson(ref), 1, "mucs");
while (HashMapIterate(mucs, &muc, (void **) &value))
{
char *chat_id = JsonValueAsString(value);
DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id);
char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id");
/* TODO */
Log(LOG_INFO, "- Plumb xmpp:%s <=> %s", muc, matrix_id);
Log(LOG_INFO, " - ID=%s", chat_id);
DbUnlock(parsee, chat_id_ref);
}
DbUnlock(parsee, ref);
}
int
Main(Array *args, HashMap *env)
{
char *db_path, *action, *exec;
int ret = EXIT_SUCCESS;
Db *parsee;
exec = ArrayGet(args, 0);
if (ArraySize(args) < 3)
{
Log(LOG_ERR, "Usage: %s [config] [action] <args>", exec);
return EXIT_FAILURE;
}
db_path = ArrayGet(args, 1);
action = ArrayGet(args, 2);
parsee = GetDB(db_path);
if (!parsee)
{
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
return EXIT_FAILURE;
}
if (StrEquals(action, "list") || StrEquals(action, "ls"))
{
ListPlumbs(parsee);
}
else if (StrEquals(action, "del"))
{
if (ArraySize(args) != 4)
{
Log(LOG_ERR, "%s: please show a !roomid:matrix.org, xmpp:mucid@jabber.org, or local hash", exec);
ret = EXIT_FAILURE;
goto end;
}
DeletePlumb(parsee, ArrayGet(args, 3));
}
end:
DbClose(parsee);
return ret;
}