Compare commits

..

46 commits

Author SHA1 Message Date
LDA
e331d110c7 [FIX] URL encode filenames 2025-06-22 14:00:12 +02:00
LDA
e617b5e8d4 [FIX/WIP] Unique redactions for moderation stanzas
Also, I'll consider Parsee dev to be dead until July 2026. J'ai le bac
et déja un projet à faire, et si je peux même pas me concentrer sur mon
futur en ne comptant pas Parsee.

Feel free to make a good XMPP bridge, I don't really even want to touch
either Matrix or XMPP anymore, you know?
2025-04-28 02:18:57 +02:00
LDA
e3749817d3 [MOD/FIX] Be even more strict with Unicode filtering
Now, only ASCII characters get to fit.
2025-04-13 10:49:40 +00:00
LDA
7b8ed08e88 [ADD] Optionally allow instance admins to ignore m.notice events 2025-04-13 10:39:15 +00:00
LDA
45250096ad [FIX] argh 2025-04-13 10:23:51 +00:00
LDA
f19ff2274b Merge branch 'master' of https://git.kappach.at/lda/Parsee 2025-03-09 11:06:42 +00:00
LDA
7454c8c597 [FIX] Fix a few bugs about edits and XEP-393 formatting 2025-03-09 11:05:55 +00:00
lda
e21ebed134 [META/ADD] Add Guix to the CHANGELOG
quick und dirty
2025-02-23 22:38:38 +00:00
lda
4eaec4687e [MERGE] Add Guix support (see #18)
Reviewed-on: https://git.kappach.at/lda/Parsee/pulls/18
2025-02-23 22:36:36 +00:00
Hank
680b4261c2 [ADD] small documentation paragraph 2025-02-23 21:48:15 +01:00
Hank
44473878d0 [ADD] add parsee guix package 2025-02-23 21:48:15 +01:00
Hank
b50b9bd615 [FIX] pin cytoplasm to the latest commit 2025-02-23 21:48:13 +01:00
Hank
a2c1de52dc [ADD] new guix.scm with cytoplasm 2025-02-23 21:33:42 +01:00
Hank
6762d1c1ce [FIX/WIP] remove .guix from gitignore 2025-02-23 21:33:42 +01:00
lda
40e3242465 [ADD] Try to renegociate an XMPP stream on failure 2025-02-19 16:14:30 +00:00
lda
b78f7b6ab3 [ADD/TOOL] Add -j flag for parsee-config 2025-02-19 15:38:22 +00:00
lda
fd1b3499b6 [ADD] Allow the main component to be used to issue commands 2025-02-19 15:32:52 +00:00
lda
fb44ad4bf6 [FIX] Go back to using room IDs
I already gave up on urandom anyways.
2025-02-18 22:52:44 +00:00
lda
0a4aa45de5 [FIX] Add proper includes 2025-02-17 20:24:06 +00:00
lda
cdbdc9345a [MOD] Don't always add two newlines 2025-02-17 17:44:09 +00:00
lda
c96f0486ff [FIX/WIP] Chrck URI before doing anything funny 2025-02-17 08:30:18 +00:00
lda
e2b014d000 [FIX/WIP] Fix replies and try fixing a bug with MUCs 2025-02-15 08:44:25 +00:00
lda
d12255b226 [FIX/WIP] Do not use room ID for plumbing 2025-02-14 21:58:42 +00:00
lda
43175e32e5 [FIX/WIP] Log out error info on ASSend 2025-02-14 20:28:49 +00:00
lda
3bef6afa5d [FIX/WIP] Try escaping room IDs
First attempt at dealing with #20
2025-02-14 19:10:26 +00:00
lda
71f3836ee1 [ADD] Use actual OS as information 2025-02-12 16:47:48 +00:00
lda
9a2d4188e2 [FIX] Do not put version in the OS field 2025-02-12 16:43:43 +00:00
lda
7f396a0379 [FIX] Write config to the proper field! 2025-02-12 08:33:05 +00:00
lda
1c51d57355 [ADD] Add accept_pings parameters
This is off by default, but highly encouraged for Synapse deployments
2025-02-12 08:24:18 +00:00
lda
176f390c4b [FIX] Use the puppet's name when pinged if possible 2025-02-11 17:18:01 +00:00
lda
f94db460ac [FIX/WIP] Fix annoying formatting bug
Fixes #16
2025-02-09 21:51:38 +00:00
lda
1936be0078 [FIX] Fix minor DB fuckup with tools
Used to open a flatfile database whenever it couldn't find an LMDB one,
which obviously caused *problems*
2025-02-08 08:45:21 +00:00
lda
c2536c2e84 [FIX] Log out some MUC information 2025-02-07 18:52:58 +00:00
lda
4f694129fc [FIX] Fix silly SEGV
Thanks, EP
2025-02-07 18:34:45 +00:00
LDA
110a1b695f [FIX] Fix the indenting 2025-02-05 08:58:25 +01:00
LDA
1e7d71f9f6 [ADD] Allow people to see component errors 2025-02-05 08:54:45 +01:00
LDA
b485298fbc [FIX] Log error if the config was not parsed 2025-01-29 19:15:23 +01:00
lda
f9de7f1750 [MOD/FIX] Be a bit more specific with component errors 2025-01-28 15:01:57 +00:00
lda
389870c5d3 [FIX] Ignore unavailable statuses
They aren't that useful, especially in MUCs.
2025-01-25 12:20:47 +00:00
lda
0facbaa5e5 [ADD/FIX/WIP] Allow reverting noflys, ignore MUC DMs (for now) 2025-01-07 15:14:47 +00:00
lda
5e1931a19f [MOD] Change media path, connect to component through another address 2025-01-06 17:09:36 +00:00
LDA
c433e31461 [DEL] Please enter a valid command
Please enter a valid command
2025-01-03 16:00:24 +01:00
LDA
c365681fcb [MOD] Notify MUCs about errors 2025-01-03 15:01:35 +01:00
LDA
86deab29af [FIX] Properly handle new contents with Matrix
No more ugly asterisks. It annoyed me enough.
2024-12-28 20:17:03 +01:00
LDA
5ddc5d3e5c [WIP/ADD] Try to separate discovery, make links nicer 2024-12-19 20:59:12 +01:00
LDA
2e566c73fc [MOD] Start not hardcoding discovery features
The eventual goal here is to have different disco replies for specific
kinds of JID, effectively allowing us to have MUCs. Yeah.
2024-11-29 13:58:49 +01:00
89 changed files with 1612 additions and 3889 deletions

16
.gitignore vendored
View file

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

82
.guix/modules/parsee.scm Normal file
View file

@ -0,0 +1,82 @@
(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,6 +13,22 @@ commit done between releases.
*There is currently no beta releases of Parsee*
## 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>
Fixes some media metadata things, replaces the build system,
tries out avatar support some more, MUC contexts, and speeds

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,70 @@
." 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)

1
guix.scm Symbolic link
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ CommandHead(CmdSetPL, cmd, argp)
char *user = HashMapGet(cmd->arguments, "user");
char *room = HashMapGet(cmd->arguments, "room");
char *pl_str = HashMapGet(cmd->arguments, "pl");
long pl = pl_str ? strtol(pl_str, NULL, 10) : 0;
long pl = strtol(pl_str, NULL, 10);
HashMap *map;

View file

@ -1,74 +0,0 @@
#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

View file

@ -1,366 +0,0 @@
#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

View file

@ -1,222 +0,0 @@
#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

View file

@ -1,207 +0,0 @@
#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

@ -1,34 +0,0 @@
#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

View file

@ -1,258 +0,0 @@
#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

View file

@ -1,98 +0,0 @@
#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

View file

@ -1,170 +0,0 @@
#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

View file

@ -1,30 +0,0 @@
#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

View file

@ -1,304 +0,0 @@
#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

View file

@ -1,86 +0,0 @@
#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

View file

@ -1,149 +0,0 @@
#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

@ -1,89 +0,0 @@
#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

@ -1,98 +0,0 @@
#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

@ -1,580 +0,0 @@
#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

@ -1,98 +0,0 @@
#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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,28 @@
#include <Glob.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
ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

154
src/XMPP/MUCServ.c Normal file
View file

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

View file

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

View file

@ -1,17 +0,0 @@
#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,8 +8,6 @@
#include <pthread.h>
#include "XMPPCommand/Internal.h"
typedef struct XMPPSession {
char *identifier;
@ -325,7 +323,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddAttr(command_xml, "node", node);
XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_id);
XMPPExecuteCommand(m, cmd, from, command_xml, NULL, node);
XMPPExecuteCommand(m, cmd, from, command_xml, NULL);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
@ -365,7 +363,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddAttr(command_xml, "node", node);
XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_given);
XMPPExecuteCommand(m, cmd, from, command_xml, x_form, node);
XMPPExecuteCommand(m, cmd, from, command_xml, x_form);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
@ -387,15 +385,3 @@ XMPPGetManagerCookie(XMPPCommandManager *manager)
{
return manager ? manager->cookie : NULL;
}
XMPPCommandFlags
XMPPGetCommandFlags(XMPPCommandManager *m, char *id)
{
XMPPCommand *cmd;
if (!m || !id)
{
return XMPPCMD_ALL;
}
cmd = HashMapGet(m->commands, id);
return cmd ? cmd->flags : XMPPCMD_ALL;
}

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -31,5 +31,4 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
SetNote("info", "Parsee data was sucessfully cleant up.");
(void) form;
(void) node;
}

View file

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

View file

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

View file

@ -15,12 +15,12 @@
#include <AS.h>
void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *muc;
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
char *key = NULL, *val = NULL;
@ -51,10 +51,9 @@ end:
Free(chat_id);
Free(muc);
(void) form;
(void) node;
}
void
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
@ -115,5 +114,4 @@ end:
Free(chat_id);
Free(muc);
(void) form;
(void) node;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Log.h>
#include <StringStream.h>
#include <XMPPCommand.h>
@ -12,33 +13,86 @@
#include "XMPPThread/internal.h"
IQFeatures *
CreateIQFeatures(void)
{
IQFeatures *ret = Malloc(sizeof(*ret));
ret->identity = ArrayCreate();
ret->adverts = ArrayCreate();
return ret;
}
void
FreeIQFeatures(IQFeatures *features)
{
size_t i;
if (!features)
{
return;
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
Free(ArrayGet(features->adverts, i));
}
ArrayFree(features->adverts);
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
Free(identity->category);
Free(identity->type);
Free(identity->lang);
Free(identity->name);
Free(identity);
}
ArrayFree(features->identity);
Free(features);
}
void
AdvertiseIQFeature(IQFeatures *f, char *feature)
{
if (!f || !feature)
{
return;
}
ArrayAdd(f->adverts, StrDuplicate(feature));
}
void
AddIQIdentity(IQFeatures *f, char *cat, char *lang, char *type, char *name)
{
XMPPIdentity *identity;
if (!f)
{
return;
}
identity = Malloc(sizeof(*identity));
identity->category = StrDuplicate(cat);
identity->type = StrDuplicate(type);
identity->lang = StrDuplicate(lang);
identity->name = StrDuplicate(name);
ArrayAdd(f->identity, identity);
}
/* Generates a SHA-256 hash of the ver field. */
char *
XMPPGenerateVer(void)
XMPPGenerateVer(IQFeatures *features)
{
char *S = NULL;
unsigned char *Sha = NULL;
Array *identities = ArrayCreate();
Array *features = ArrayCreate();
size_t i;
/* Initialise identity table, to be sorted */
#define IdentitySimple(cat, Type, Name) { \
XMPPIdentity *id = Malloc(sizeof(*id)); \
id->category = cat; \
id->lang = NULL; \
id->type = Type; \
id->name = Name; \
ArrayAdd(identities, id); }
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) ArrayAdd(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
ArraySort(identities, IdentitySort);
for (i = 0; i < ArraySize(identities); i++)
ArraySort(features->identity, IdentitySort);
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(identities, i);
XMPPIdentity *identity = ArrayGet(features->identity, i);
char *id_chunk = StrConcat(7,
identity->category, "/",
identity->type, "/",
@ -50,10 +104,10 @@ XMPPGenerateVer(void)
Free(id_chunk);
}
ArraySort(features, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features); i++)
ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features->adverts); i++)
{
char *feature = ArrayGet(features, i);
char *feature = ArrayGet(features->adverts, i);
char *tmp = S;
S = StrConcat(3, S, feature, "<");
Free(tmp);
@ -64,16 +118,64 @@ XMPPGenerateVer(void)
S = Base64Encode((const char *) Sha, 20);
Free(Sha);
ArrayFree(features);
for (i = 0; i < ArraySize(identities); i++)
{
XMPPIdentity *identity = ArrayGet(identities, i);
/* We don't have to do anything here. */
Free(identity);
}
ArrayFree(identities);
return S;
}
void
XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features)
{
XMLElement *c;
char *ver;
if (!presence || !features)
{
return;
}
ver = XMPPGenerateVer(features);
c = XMLCreateTag("c");
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
XMLAddAttr(c, "hash", "sha-1");
XMLAddAttr(c, "node", REPOSITORY);
XMLAddAttr(c, "ver", ver);
Free(ver);
XMLAddChild(presence, c);
}
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,13 +94,15 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item)
AddTextField(vcard, "fn", name);
AddTextField(vcard, "nickname", mxid);
AddURIField(vcard, "url", REPOSITORY);
AddURIField(vcard, "url", "https://kappach.at/parsee");
AddURIField(vcard, "url", "https://matrix.org");
AddURIField(vcard, "url", m_to);
AddTextField(
vcard,
"note",
"This is a bridged Matrix user, from " NAME "."
"This is a bridged Matrix user, from Parsee."
);
Free(mxid);
Free(name);

View file

@ -56,6 +56,8 @@ XMPPDispatcher(void *argp)
if (!stanza)
{
/* TODO: We shouldn't be busywaiting. Even with a sleep call.
*/
UtilSleepMillis(10);
continue;
}
@ -65,12 +67,8 @@ XMPPDispatcher(void *argp)
Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name);
}
if (ExtensionPushStanza(args->exts, stanza, STANZA_RAW))
if (ManageMUCStanza(args->muc, stanza))
{
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Stanza was vetoed by an extension.");
}
XMLFreeElement(stanza);
continue;
}
@ -140,7 +138,6 @@ bool
XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
{
ParseeData *args = XMPPGetManagerCookie(m);
XMPPCommandFlags flags;
char *trimmed_from;
char *from;
char *chat_id;
@ -155,24 +152,28 @@ XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from));
Free(trimmed_from);
Free(chat_id);
flags = XMPPGetCommandFlags(m, id);
if (flags == XMPPCMD_ALL)
{
return true;
}
else if (flags == XMPPCMD_MUC)
{
return is_muc;
}
else if (flags == XMPPCMD_ADMINS)
{
bool is_admin;
trimmed_from = ParseeTrimJID(from);
is_admin = ParseeIsAdmin(args, trimmed_from);
Free(trimmed_from);
return is_admin;
#define XMPP_COMMAND(f,l,n,t,s) \
if (StrEquals(n, id)) \
{ \
if (l == XMPPCMD_ALL) \
{ \
return true; \
} \
else if (l == XMPPCMD_MUC) \
{ \
return is_muc; \
} \
else if (l == XMPPCMD_ADMINS) \
{ \
bool is_admin; \
trimmed_from = ParseeTrimJID(from); \
is_admin = ParseeIsAdmin(args, trimmed_from); \
Free(trimmed_from); \
return is_admin; \
} \
}
XMPPCOMMANDS
#undef XMPP_COMMAND
return false;
}
@ -198,14 +199,13 @@ ParseeXMPPThread(void *argp)
XMPPCommand *cmd;
#define XMPP_COMMAND(f,l,n,t,s) \
cmd = XMPPBasicCmd( \
l, n, t, f \
n, t, f \
); \
s \
XMPPRegisterCommand(info.m, cmd);
XMPPCOMMANDS
#undef XMPP_COMMAND
}
ExtensionRequestCommands(args->exts);
info.pep_manager = CreatePEPManager(args, &info);
{
PEPManagerAddEvent(
@ -244,66 +244,96 @@ ParseeXMPPThread(void *argp)
}
}
while (true)
while (!args->halted)
{
char *id;
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
while (true)
{
/* Try to check if an error is abound */
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
char *id;
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{
Log(LOG_DEBUG, "RECEIVED EOF.");
/* Try to check if an error is abound */
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "RECEIVED EOF.");
}
break;
}
break;
}
if (args->verbosity >= PARSEE_VERBOSE_STANZA)
{
Stream *output = StreamStderr();
StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
XMLEncode(output, stanza);
StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
if (args->verbosity >= PARSEE_VERBOSE_STANZA)
{
pthread_mutex_lock(&await->cond_lock);
await->stanza = stanza;
pthread_cond_signal(&await->condition);
pthread_mutex_unlock(&await->cond_lock);
Stream *output = StreamStderr();
StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
XMLEncode(output, stanza);
StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
HashMapDelete(await_table, id);
id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
{
pthread_mutex_lock(&await->cond_lock);
await->stanza = stanza;
pthread_cond_signal(&await->condition);
pthread_mutex_unlock(&await->cond_lock);
HashMapDelete(await_table, id);
pthread_mutex_unlock(&await_lock);
continue;
}
pthread_mutex_unlock(&await_lock);
continue;
}
pthread_mutex_unlock(&await_lock);
}
/* Push it into the stanza FIFO. A dispatcher thread should then
* be able to freely grab a value(locked by a mutex). We can't make
* dispatchers read stanzas on their own, since that's _not_ how
* streams work, but this should mitigate some issues, and allow a
* few threads to be busy, while the rest of Parsee works. */
PushStanza(&info, stanza);
/* Push it into the stanza FIFO. A dispatcher thread should then
* be able to freely grab a value(locked by a mutex). We can't make
* dispatchers read stanzas on their own, since that's _not_ how
* streams work, but this should mitigate some issues, and allow a
* few threads to be busy, while the rest of Parsee works. */
PushStanza(&info, stanza);
}
pthread_mutex_lock(&args->halt_lock);
if (!args->halted)
{
Log(LOG_WARNING, "XMPP server is closing stream...");
for (size_t i = 0; i < 50; i++)
{
UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */
Log(LOG_WARNING, "Restarting XMPP stream.");
/* This is the part where a new connection is being considered */
XMPPFinishCompStream(jabber);
XMPPEndCompStream(jabber);
args->jabber = XMPPInitialiseCompStream(
args->config->component_addr,
args->config->component_host,
args->config->component_port
);
jabber = args->jabber;
if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret))
{
/* Oops, there is something wrong! */
Log(LOG_ERR, "Couldn't authenticate to XMPP server");
XMPPEndCompStream(jabber);
args->jabber = NULL;
jabber = NULL;
if (i == 4)
{
pthread_mutex_unlock(&args->halt_lock);
break;
}
}
}
}
pthread_mutex_unlock(&args->halt_lock);
}
pthread_mutex_lock(&args->halt_lock);
if (!args->halted)
{
Log(LOG_WARNING, "XMPP server is closing stream...");
Log(LOG_WARNING, "Stopping %s...", NAME);
error = true;
}
pthread_mutex_unlock(&args->halt_lock);
info.running = false;
for (i = 0; i < info.available_dispatchers; i++)

View file

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

View file

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

View file

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

View file

@ -32,13 +32,17 @@
IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \
IdentitySimple("component", "generic", "Parsee's component")
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct IQFeatures {
Array *identity;
Array *adverts;
} IQFeatures;
typedef struct XMPPIdentity {
char *category, *type, *lang, *name;
} XMPPIdentity;
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct XMPPThread XMPPThread;
typedef struct XMPPThreadInfo {
/* A FIFO of stanzas inbound, to be read by dispatcher
@ -65,9 +69,33 @@ struct XMPPThread {
int ICollate(unsigned char *cata, unsigned char *catb);
int IdentitySort(void *idap, void *idbp);
IQFeatures * CreateIQFeatures(void);
void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *name);
void AdvertiseIQFeature(IQFeatures *, char *feature);
void FreeIQFeatures(IQFeatures *);
/**
* 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 * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id);
char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza);
void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event);
bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza);
HashMap * ShoveStanza(HashMap *content, XMLElement *stanza);
@ -81,6 +109,9 @@ void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
bool ServerHasXEP421(ParseeData *data, char *from);
char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force);
#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL)
PEPManager * CreatePEPManager(ParseeData *data, void *cookie);
void * PEPManagerCookie(PEPManager *manager);
void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event);

View file

@ -1,81 +0,0 @@
#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

39
src/include/MUCServ.h Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -89,31 +89,30 @@ Main(Array *args, HashMap *env)
glob = ArrayGet(args, 2);
parsee = GetDB(db_path);
if (parsee)
if (!parsee)
{
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
}
if (glob)
{
Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path);
AddAdmin(parsee, glob);
Log(LOG_INFO, "Successfully added glob %s.", glob);
Log(LOG_INFO, "*I'm jealous of all these admins!*");
DbClose(parsee);
return EXIT_SUCCESS;
}
/* List admins */
Log(LOG_INFO, "Admin list:");
LogConfigIndent(LogConfigGlobal());
ListAdmins(parsee);
LogConfigUnindent(LogConfigGlobal());
if (glob)
{
Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path);
AddAdmin(parsee, glob);
Log(LOG_INFO, "Successfully added glob %s.", glob);
Log(LOG_INFO, "*I'm jealous of all these admins!*");
DbClose(parsee);
return EXIT_SUCCESS;
}
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
/* List admins */
Log(LOG_INFO, "Admin list:");
LogConfigIndent(LogConfigGlobal());
ListAdmins(parsee);
LogConfigUnindent(LogConfigGlobal());
DbClose(parsee);
return EXIT_SUCCESS;
}

View file

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

View file

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

152
tools/plumb.c Normal file
View file

@ -0,0 +1,152 @@
/* plumb.c - Small utility to manage plumbings from a shutoff instance.
* ============================================================
* TODO: write other commands, and move some code to common.h
*
* Under CC0, as its a rather useful example of a Parsee tool.
* See LICENSE for more information about Parsee's licensing. */
#include "common.h"
#include <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;
}