Compare commits

..

229 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
LDA
5ff92dda3d [ADD] Basic header check in configure
It doesn't exactly conform to C99 standards, but it is good enough for
Parsee as a whole.
2024-11-20 22:05:54 +01:00
LDA
56433fc69c [DEL] Remove 64KB avatar hardlimit 2024-11-18 21:46:59 +01:00
LDA
5d13410ac4 [ADD] Ping homeserver to see if Parsee works 2024-11-17 16:22:10 +01:00
LDA
7f41a85a8a [FIX] Oh, and set the version 2024-11-11 11:23:10 +01:00
LDA
954c588310 [FIX] Don't check for upgrades on start. 2024-11-11 11:00:26 +01:00
LDA
dddf1680a0 [RELEASE/FIX] Fix ban-unlinks, actually get 0.2. 2024-11-08 17:43:41 +01:00
LDA
960599d983 [FIX/ADD/WIP] Fix very silly printf, media flags 2024-10-30 09:21:20 +01:00
LDA
033dba6840 [META] Fix typo 2024-10-29 16:58:48 +01:00
LDA
cf3bb5260b [CI/FIX] Fix potentially uninitialised key-values 2024-10-29 14:51:58 +01:00
LDA
064040c18f [ADD/WIP] Chat settings
Right now, they are currently unused. Extensions, and Parsee itself
will be able to use those, though.
2024-10-29 14:44:55 +01:00
LDA
55ac682d26 [MOD] Timestamp Matrix messages 2024-10-28 13:26:25 +01:00
LDA
ca5f8fd5c5 [CI/FIX] Fix unused argument again (again) 2024-10-28 10:51:49 +01:00
LDA
947c2d48d2 [CI/FIX] Fix unused argument again 2024-10-28 10:47:54 +01:00
LDA
dcd31201ab [CI/FIX] Fix unused argument 2024-10-28 10:44:23 +01:00
LDA
6aef0349b8 [CI/FIX] Make CI happy again 2024-10-28 09:08:08 +01:00
LDA
7f7ab41afa [ADD] Add a few basic MUC commands 2024-10-28 08:57:17 +01:00
LDA
d4371ac30b [META] Update CHANGELOG 2024-10-27 20:40:25 +01:00
LDA
9a16d96323 [ADD] Filtering XMPP commands
This allows us to have commands apply to admins or MUCs only(which would
help with many things I want to implement).
2024-10-27 19:08:37 +01:00
LDA
7c60ab28cb [ADD] Proper stanza limits 2024-10-25 18:03:05 +02:00
LDA
ce8bbb5540 [MOD] Hide avatar code behind another function 2024-10-25 13:46:55 +02:00
LDA
4bc2d1606a Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-10-25 13:27:09 +02:00
LDA
fced112691 [FIX] Fix CI 2024-10-24 13:26:46 +02:00
LDA
4b5a759ce7 [FIX] Fix uninitialised variables
I'm not sure why Debian's GCC didn't catch this, but NetBSD's did.
Another reason to add CI checks for NetBSD, then, though I am not sure
how to do it safely through VMs(and I doubt Docker/Podman would help me
for weird architectures). Hm.
2024-10-22 10:26:38 +02:00
LDA
b0cf0961a3 [FIX] Make codebase *slightly* NetBSD-friendly
I need to make it so that NetBSD targets get their own
correctness/build checks in CI. I also had to do some hacks with
Cytoplasm, will make a PR for those soon.
2024-10-22 10:21:54 +02:00
LDA
44d6df3be8 [FIX/CI] Make the C compiler less stupid
(You may notice that the dates for these commits are off. I'm
currently writing them without an Internet connection, and the
device I'm writing this through doesn't have an RTC. Oops!)
2024-10-20 15:12:08 +02:00
LDA
02a89270c0 [FIX] Kill Parsee on unexcepted stream closure 2024-10-20 12:53:07 +02:00
LDA
ff5f085b7f [FIX/MOD] Stanza send function, fix some errors
Negociating stanza sizes seems to be real trouble. Also this code is now
`-fanalyzer'-safe!
Also kills the last GNUMakefile present. It wasn't even used...
2024-10-19 16:58:48 +02:00
LDA
d9b5141eb5 Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-10-17 17:13:08 +02:00
LDA
bb32eb48a6 Merge branch 'fix-configure' 2024-10-17 16:57:14 +02:00
Xavier Del Campo Romero
2074d8e768
[FIX] configure.c: Do not assume git
If parsee were downloaded from sources other than git-clone(1) (e.g.: a
tarball), perror(3) fails and/or git(1) is not available on the system,
"git remote get-url origin" would have unexpected results or even return
a NULL pointer, which would cause undefined behaviour on
strchr(repo, ...);

In such conditions, a placeholder string, namely "N/A", is strdup(3)ed
to `repo`.
2024-10-17 07:37:01 +02:00
Xavier Del Campo Romero
e0f76e7929
[FIX] configure.c: Add missing -I $(CYTO_INC)
Some tools depend on header files defined by Cytoplasm.
2024-10-17 07:00:59 +02:00
Xavier Del Campo Romero
a66021bf46
[FIX] configure.c: Improve recursion rule
So far, collect_sources was called recursively when the entry name did
not match a pre-defined list. However, this assumption broke with files
such as `etc/media/README`, that were not in the list.

Therefore, a deterministic way to distinguish files from directories is
to rely on the S_ISDIR macro. This avoids the need for a pre-defined
list.
2024-10-17 06:45:42 +02:00
Xavier Del Campo Romero
ecbc211003
[FIX] configure.c: Fix '.' and '..' detection
Otherwise, hidden files such as '.file.<ext>' would be ignored.
2024-10-17 06:44:18 +02:00
LDA
1be19d4b8d [CI/FIX] Proper casts 2024-10-11 20:03:41 +02:00
LDA
27223a5896 [FIX/CI] Listen to CI 2024-10-11 19:58:28 +02:00
LDA
147e430a47 [FIX] Do NOT use strlen in a loop
Never. Do. This. Never. It should never cross your mind. Doing so will
punish you. Basic computer science concepts will tell you it's O(n^2).
And it WILL actually matter. Never. Never. Never. Never. Never. Never.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
Anyways, also fixes licensing year. On est en octobre et il comprend
toujours pas qu'on est plus en 2023.
2024-10-11 19:49:19 +02:00
LDA
13a9d23fa0 [FIX/ADD] Fix YAML generation, keep track of time 2024-10-11 16:09:21 +02:00
LDA
a48c3ba126 [ADD] Have avatars on first-message too 2024-10-08 17:27:25 +02:00
LDA
94090a8c97 [MOD] Rewrite the status code slightly
Also handle error-based kicks
2024-10-05 09:43:12 +02:00
LDA
179fd405db [FIX] Do not listen for kick requests
They may be sent for scenarios where an unlink is NOT wanted.
2024-10-05 09:20:49 +02:00
LDA
b7c360d528 [FIX] Handle logic mistake in plumbing 2024-10-04 12:09:56 +02:00
LDA
17474bda0f [FIX] (Try to) fix the memory leak 2024-10-03 07:53:39 +02:00
LDA
d585134ce1 [ADD/WIP] Temporary vCard avatard
Gajim still reacts weirdly.... also it leaks memory related to avatars,
so that sucks...
2024-10-02 21:13:28 +02:00
LDA
9ea8f35c49 [ADD/WIP] Try dealing in VCards 2024-10-01 06:59:13 +02:00
LDA
d989331716 [MOD] Add width/height information when possible 2024-09-30 06:11:31 +02:00
LDA
09d38993bb [ADD] Add XMPP commands for managing whitelists 2024-09-29 09:37:46 +02:00
LDA
a38e14e029 [FIX] Fix unused variables 2024-09-28 19:04:23 +02:00
LDA
0b00a665bf [ADD] MUC whitelists, part II 2024-09-28 15:58:41 +02:00
LDA
af2d08a431 [ADD/WIP] Start introducing whitelist command
The whitelist system will also be managable from XMPP and Matrix, and
will be for MUC/room management.
2024-09-28 13:24:38 +02:00
LDA
3ceae7b053 I honestly don't know what to call it.
It's not a 0.2 release, btw.
2024-09-26 19:07:46 +02:00
LDA
e7ba1fa48d [MOD] Separate the unlink in another function 2024-09-26 11:11:39 +02:00
LDA
749df0feb1 [ADD/WIP] Basic support for opting-out of bridging 2024-09-25 22:09:11 +02:00
LDA
0f3253a385 [FIX] Fix unfreed value on renames 2024-09-24 07:08:25 +02:00
LDA
2324f9afc0 [MOD/META] Clarify LICENSE more, fix chatstates 2024-09-22 18:36:53 +02:00
LDA
63285ac24a [FIX] Fix a very silly mistake 2024-09-22 15:13:12 +02:00
LDA
dec0d1f3d9 [FIX/WIP] Start making Parsee go vroom 2024-09-22 11:18:41 +02:00
LDA
c2ea3807ec [MOD/FIX] Licensewerk, start counting by codepoint
XEP-0426 came out of the blue, and it *hit* me!
2024-09-21 18:26:35 +02:00
LDA
dba3dcc85f [FIX] Fix more unistr fuckups 2024-09-21 14:11:04 +02:00
LDA
b9954c06ce [FIX] Apparently 0b isn't valid C99 2024-09-21 13:48:09 +02:00
LDA
6167732e83 [FIX] BMP for MUC nicks, dip toes in vCard avatars
Can, your, Bifrost, Do, That. -lh
2024-09-21 13:18:59 +02:00
LDA
9b4d97b033 [FIX] Fix subscription hash's warns 2024-09-20 07:01:35 +02:00
LDA
6e9ba8f0ee [FIX] Fix media C99 fuckups 2024-09-19 23:30:59 +02:00
LDA
e54332e376 [MOD] Basic work to get XMPP avatars through PEP
Attaboy!
2024-09-19 23:24:46 +02:00
LDA
f31a9c37b6 Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-09-19 10:09:03 +02:00
lda
c607a99004 [FIX] Get rid of unused variable
It's joever...
2024-09-19 06:54:17 +00:00
LDA
7ee5c055f4 [MOD] Actually start noting down presence requests 2024-09-18 15:39:52 +02:00
LDA
f666f39b7c [MOD] Add CHANGELOG, start PEPing 2024-09-18 07:58:21 +02:00
LDA
420c05690f [FIX] Catch SIGPIPEs 2024-09-18 05:55:47 +02:00
LDA
f96e0a95bd [CI] Just generate ZIPs 2024-09-17 21:45:26 +02:00
LDA
5ffd413e67 [CI/FIX] Fix YAML issue 2024-09-17 10:33:46 +02:00
LDA
9d67cb6165 [CI] Silly me. 2024-09-17 10:24:26 +02:00
LDA
9ff70bc0ed [CI] Push my luck with strategies 2024-09-17 10:21:27 +02:00
LDA
05be7fe249 [MOD] Do some tricks to make formatting nicer
It just didn't make sense on basic messages and Element Android.
Fluffy behaved as excepted, though.
2024-09-16 09:32:44 +02:00
LDA
0cccef7194 [CI] I hate Docker, again 2024-09-15 16:22:03 +02:00
LDA
f8b834c652 [CI] Help 2024-09-15 15:40:50 +02:00
LDA
1ce2eea9d8 [CI] Use single runs-on 2024-09-15 15:24:54 +02:00
LDA
cb70d599bf [CI[ Try to get ARM64. 2024-09-15 14:15:10 +02:00
LDA
d99b827d5c [CI] Fix typo in generation 2024-09-15 13:47:51 +02:00
LDA
dd6dede2e8 [CI] Use the right URL 2024-09-15 13:43:56 +02:00
LDA
bdb1f46c77 [CI] Use custom configure 2024-09-15 13:40:08 +02:00
LDA
613822e40c [CI] Base on a Mbed release 2024-09-15 13:17:54 +02:00
LDA
2e4314be48 [CI] Grab the right branch 2024-09-15 12:33:29 +02:00
LDA
1f7a220a8f [CI] Build with submodules 2024-09-15 12:26:36 +02:00
LDA
7c4333f137 [CI] Python hjinks 2024-09-15 12:24:20 +02:00
LDA
901a88c638 [CI] Ow. 2024-09-15 12:12:25 +02:00
LDA
4a2160503b [CI] Try to Mbed things out 2024-09-15 12:07:30 +02:00
LDA
9f1c2046fd [FIX] Fix out media from Matrix 2024-09-13 14:29:51 +02:00
LDA
4164eb5ed2 [CI] Try compressing the compress 2024-09-13 12:00:46 +02:00
LDA
63772d50ab [CI] I hate Forgejo. 2024-09-13 06:10:25 +02:00
LDA
92db543d78 [CI] Bloody hell... 2024-09-13 00:02:35 +02:00
LDA
19cf159f21 [CI] Crank compression to 9 2024-09-12 23:41:01 +02:00
LDA
fc75dc52aa [CI] This ought to be it 2024-09-12 23:14:05 +02:00
LDA
0ba708e28f [CI] Is he stupid??? 2024-09-12 23:05:14 +02:00
LDA
75bbe69633 [CI/FIX] Use limits.h 2024-09-12 22:58:13 +02:00
LDA
b10f14d067 [CI] What. 2024-09-12 22:35:41 +02:00
LDA
eb25ce3e2b [CI] Silly me, I need to build configure 2024-09-12 22:29:07 +02:00
LDA
92d33fa641 [MOD/CI/BUILD] Static builds, via the native tools 2024-09-12 22:17:35 +02:00
LDA
a110b44ad6 [CI/FIX] 2024-09-12 20:11:54 +02:00
LDA
90ddce4757 [CI/FIX] Aya... 2024-09-12 20:09:35 +02:00
LDA
e0e398bfb5 [META/CI] Thanks CI for catching this! 2024-09-12 20:06:33 +02:00
LDA
9352bf9bba [META] Death to GNUMakefile and build.c
Soon bound to add a -s flag for static build.
2024-09-12 20:02:40 +02:00
LDA
355a9d9b99 [CI] Oops 2024-09-11 15:15:46 +02:00
LDA
0586e863e7 [CI] Try using directory 2024-09-11 15:10:03 +02:00
LDA
619f49f8c8 [CI] Don't actually suffix ZIP, I think 2024-09-11 12:59:20 +02:00
LDA
5c7189f518 [CI] Is he stupid? 2024-09-11 12:53:27 +02:00
LDA
b55457d5fe [CI] Actual tgz.zip 2024-09-11 12:43:11 +02:00
LDA
79e65f0822 [CI] Label artifact as tar.gz.zip 2024-09-11 12:42:31 +02:00
LDA
79c94759da [CI] TFW it's broken 2024-09-11 12:33:26 +02:00
LDA
5c5540ffa6 [CI] By Jove, I think I got it! 2024-09-11 12:25:46 +02:00
LDA
9e4348c6e3 [CI] AAA 2024-09-11 12:20:21 +02:00
LDA
0e746465c6 [CI] Help. 2024-09-11 12:13:14 +02:00
LDA
aef19d5eaf [CI] Should do it, I think 2024-09-11 12:03:15 +02:00
LDA
0c402d8032 [CI] it was, infact, not the last commit 2024-09-11 11:56:49 +02:00
LDA
e9b36730b5 [CI] C'est le commit final 2024-09-11 11:49:26 +02:00
LDA
7ab99982f5 [CI] Escape $s 2024-09-11 11:28:15 +02:00
LDA
2955787aa2 [CI] Please? 2024-09-11 11:17:31 +02:00
LDA
4496f377fd [CI] Even less prefix 2024-09-11 11:10:33 +02:00
LDA
4dcd989c8b [CI] Use the PREFIX less 2024-09-11 10:05:07 +02:00
LDA
eb9512cde5 [CI] Do not use checkout 2024-09-11 09:58:50 +02:00
LDA
89ab1b57f0 [CI] Please? 2024-09-11 09:40:05 +02:00
LDA
d9bea609dd [CI] This should(n't) do the trick 2024-09-11 09:33:02 +02:00
LDA
e8fb9b9641 [CI] Try to create a static CI job 2024-09-11 09:15:20 +02:00
LDA
eefd5f0389 [MOD] Don't make the text so large 2024-09-10 22:28:55 +02:00
LDA
907ac13da9 [FIX] Make Parsee build on GCC without warns 2024-09-10 22:20:13 +02:00
LDA
1d188069db [FIX] Fix other little CI thing 2024-09-10 21:51:09 +02:00
LDA
696def187c [MOD/FIX] Set history limit, change logo, CI 2024-09-10 21:47:08 +02:00
LDA
42f02010d4 [CI] Don't doublequote 2024-09-10 21:43:25 +02:00
LDA
a3dc90f93e [CI] What? 2024-09-10 21:41:21 +02:00
LDA
3ae262c452 [CI] Be stringent on flags 2024-09-10 21:39:08 +02:00
LDA
a7c06bdb5c [CI] Exit CI hell... for now. 2024-09-10 21:29:08 +02:00
LDA
e1b044b268 [CI] Set prefix to /usr 2024-09-10 21:13:05 +02:00
LDA
5149a37d2e [CI] Use local mirror of Cytoplasm 2024-09-10 21:10:23 +02:00
LDA
9cba44f69b [CI] Set a depth of 1 2024-09-10 20:39:45 +02:00
LDA
aa8faec486 [CI] Try getting in the right directory 2024-09-10 20:33:04 +02:00
LDA
7314be5aeb [CI] Silly me! 2024-09-10 20:29:38 +02:00
LDA
60f3dc12c1 [CI] Hopefully this works 2024-09-10 20:28:14 +02:00
LDA
4e22fb6f04 [CI] Install git too 2024-09-10 20:21:39 +02:00
LDA
c8925dbe00 [CI] checkout is stupid 2024-09-10 20:19:20 +02:00
LDA
1f31ce2c35 [CI] Use proper tag 2024-09-10 20:15:41 +02:00
LDA
f1d864c975 [CI] User input moment 2024-09-10 20:00:51 +02:00
LDA
fdc0dfbcb6 [CI] Try running in a container 2024-09-10 19:56:08 +02:00
LDA
971f8ba3aa [CI] Mess with waters 2024-09-10 19:22:01 +02:00
LDA
2a6a3300e6 [CI] Start entering CI hell 2024-09-10 19:07:12 +02:00
LDA
488ab92a90 [MOD] Modify retention periods 2024-09-10 10:52:40 +02:00
LDA
7593225e18 [MOD/META] Correct README, make sure edit is NULL 2024-09-10 08:01:32 +02:00
LDA
aec3c87aee Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-09-09 19:25:27 +02:00
LDA
65c6e79fb2 [MOD/META] Change README/CHANGELOG 2024-09-09 19:24:55 +02:00
LDA
9843a0057a [RELEASE] Officially call in 0.1's release
ADDS: NOTHING lol
Time to create a tag too lol... and fix that potential
deadlock.......

Also, the TWIM. Don't forget the TWIM. And the Fedi post.
And the AUR package. And the static builds(ARM is gonna take forever...)
Fun times over at Parsee.
2024-09-08 23:55:04 +02:00
LDA
9775bd8d4e [MOD] Read the config file for tool DB access 2024-09-08 16:12:27 +02:00
LDA
5d268a71a5 [FIX] Fix out edits 2024-09-08 09:11:58 +02:00
LDA
53739dd42d [FIX] Leaves now actually do matter on Matrix 2024-09-07 18:37:45 +02:00
LDA
0ec028d458 [ADD/MOD] CFLAGS/LDFLAGS and version checks 2024-09-07 14:49:30 +02:00
LDA
ad7f4f20e1 [FIX] Broken-ass replies 2024-09-06 07:43:19 +02:00
LDA
0ce72b52e9 [DEL] Remove wizard 2024-09-05 10:05:25 +02:00
LDA
6c5b85aea6 [MOD] Update manpages 2024-09-05 08:01:19 +02:00
LDA
24c6e70cb7 [MOD] Incremental builds, use cc 2024-09-05 06:17:44 +02:00
LDA
8ce69d0829 [ADD/WIP] build.c script, raise to 0.1 2024-09-02 21:42:40 +02:00
LDA
34bd036ab7 [MOD] Start preparing for an alpha
The (9)th.
2024-09-01 21:19:24 +02:00
LDA
80bab890f6 [MOD] Start separating build config, HTML in edits 2024-08-31 17:59:39 +02:00
LDA
1f3d738bb4 [MOD] Import PNG media. 2024-08-31 13:16:58 +02:00
LDA
9d9453f96a [MOD] Cache disco 2024-08-30 18:13:24 +02:00
LDA
063314b081 [MOD] Cache XEP-0421 support 2024-08-29 23:47:31 +02:00
LDA
3366fcb759 [MOD] Be stringent about OIDs, use fmemopen 2024-08-29 15:31:11 +02:00
LDA
55674c369b [MOD] Avoid allocating memory in HMAC 2024-08-26 21:07:55 +02:00
LDA
39cc04fc2e [MOD] HMAC-based mediachecking 2024-08-25 23:00:31 +02:00
LDA
692cb8aa6f [MOD] Verbosity levels, avatar cleanser
I have a weird bug where Parsee seems to hang on a Db request, been
tryign to figure that otu since a while but can't quite put my finger on
where.
You can have this commit, as a treat.
2024-08-24 19:36:37 +02:00
LDA
3a60b773c9 Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-08-22 14:49:59 +02:00
LDA
27e0b96ecd [MOD] Add manpages
Oops, gitignore didn't track these
2024-08-22 14:49:38 +02:00
lda
2cc8e16ba2 [MOD] Allow other compilers 2024-08-22 14:40:34 +02:00
lda
69d913d92b Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-08-22 14:39:25 +02:00
lda
54c5331835 [MOD] Try to make Parsee work with TCC 2024-08-22 14:38:02 +02:00
LDA
8042ccc0cc [ADD] Add basic Matrix ID parser 2024-08-22 00:04:50 +02:00
LDA
fb511b4df0 [MOD/ADD] Separate AS code, XMPP reactions removal 2024-08-21 13:51:52 +02:00
LDA
1b62072a3a [MOD] Start separating Parsee functions more 2024-08-19 20:25:00 +02:00
LDA
4c158ea186 [MOD] Use specific SHA1/256 abstraction functions 2024-08-19 16:40:30 +02:00
LDA
6cd3df21db [ADD] -O3, -C [config] flag 2024-08-19 14:51:34 +02:00
LDA
4b2ede6a66 [ADD] Help screen 2024-08-19 11:40:29 +02:00
LDA
1f747dd016 [FIX] Fix potential race condition 2024-08-19 00:24:00 +02:00
LDA
c975dba852 [MOD] Remove the last [p]based nicks, table moving 2024-08-18 23:52:47 +02:00
LDA
198bdb98e9 [MOD] Conceal Parsee IDs
Those are meant to be secrets, unknowns. No one, except Parsee itself
should be aware about it. I may add a `parsee-regen` tool, but it shall
not show it.
2024-08-18 16:41:52 +02:00
LDA
59cbd8b22d [MOD] Hash-based name conflict resolution
Still basic, but should prevent basic name issues.
2024-08-18 16:11:12 +02:00
LDA
c0c13883d1 [MOD] Wow, it can save the world! 2024-08-18 10:13:07 +02:00
LDA
aa9b68e02d [ADD/WIP] Parsee IDs, -v flag 2024-08-16 16:38:21 +02:00
133 changed files with 8900 additions and 2915 deletions

View file

@ -0,0 +1,85 @@
name: Builds static Parsee
on: [push]
jobs:
compile:
strategy:
matrix:
distro:
- debian
arch:
- amd64
- arm64
runs-on:
- ${{ matrix.distro }}
- ${{ matrix.arch }}
container:
image: ${{ matrix.distro }}
steps:
- name: Install LMDB+OpenSSL tools
run: |
echo $(uname -a) $(env | grep container)
apt update -y
apt install -y build-essential liblmdb-dev nodejs libssl-dev git wget python3 python3-pip bzip2
- name: Clone everything
run: |
mkdir -p repos
git clone --depth=1 https://git.musl-libc.org/git/musl repos/musl
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.1/mbedtls-3.6.1.tar.bz2
tar -xvf mbedtls-3.6.1.tar.bz2
mv mbedtls-3.6.1/ repos/mbed
git clone --depth=1 https://github.com/LMDB/lmdb repos/lmdb
git clone -b add-mbed --single-branch https://git.telodendria.io/lda/Cytoplasm repos/cyto
echo "PREFIX=$GITHUB_WORKSPACE/usr" >> $GITHUB_ENV
echo "PATH=$GITHUB_WORKSPACE/usr/bin:$PATH" >> $GITHUB_ENV
echo "INCLUDE_PATH=$GITHUB_WORKSPACE/usr/include" >> $GITHUB_ENV
echo "LIBRARY_PATH=$GITHUB_WORKSPACE/usr/lib" >> $GITHUB_ENV
- name: Build musl
run: |
cd repos/musl
./configure --prefix=${PREFIX}
make -j$(nproc)
make install -j$(nproc)
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
- name: Build MbedTLS
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/mbed
python3 -m pip install -r scripts/basic.requirements.txt --break-system-packages
make CC=musl-gcc -j$(nproc)
make install DESTDIR=${PREFIX} -j$(nproc)
- name: Build LMDB
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/lmdb/libraries/liblmdb
make CC=musl-gcc
make install prefix=${PREFIX}
- name: Build Cytoplasm
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/cyto
git fetch
rm configure
wget https://kappach.at/configure
chmod +x configure
./configure --cc=musl-gcc --prefix=${PREFIX} --with-lmdb --with-mbed
make -j$(nproc)
make install
- name: Clone/Build Parsee
run: |
git clone https://git.kappach.at/${{ github.repository}} parsee
cd parsee
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
musl-gcc -static configure.c -o configure && ./configure -s -l
make CC=musl-gcc CYTO_INC=${PREFIX}/include CYTO_LIB=${PREFIX}/lib LDFLAGS=-s
make PREFIX=bins install
- name: Create a final archive
run: |
cd parsee
echo "NAM=parsee-$(uname)-$(uname -m)" >> $GITHUB_ENV
echo "DIR=$PWD/bins" >> $GITHUB_ENV
- name: Upload it all(as a ZIP)
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: ${{ env.NAM }}
path: ${{ env.DIR }}
compression-level: 9

View file

@ -0,0 +1,41 @@
name: Checks Parsee's correctness on GCC+Debian
on: [push]
jobs:
compile:
strategy:
matrix:
distro:
- debian
arch:
- amd64
runs-on:
- ${{ matrix.distro }}
- ${{ matrix.arch }}
container:
image: ${{ matrix.distro }}
steps:
- name: Install LMDB+OpenSSL tools
run: |
echo $(uname -a) $(env | grep container)
apt update -y
apt install -y build-essential liblmdb-dev nodejs libssl-dev git
- name: Clone/Configure Cytoplasm
run: |
git clone https://git.kappach.at/KappaChat/Cytoplasm --depth=1
cd Cytoplasm
./configure --with-lmdb --prefix=/usr
- name: Build Cytoplasm
run: 'cd Cytoplasm && make -j$(nproc)'
- name: Install Cytoplasm
run: 'cd Cytoplasm && make install'
- name: Clone Parsee
uses: actions/checkout@v3
- name: Build Parsee
run: |
cc configure.c -o configure && ./configure
echo 'CFLAGS=-Werror -Wall -Wextra -pedantic -fanalyzer' >> build.conf
cat build.conf
make && make ayadoc
- name: Install Parsee
run: 'make install'

22
.gitignore vendored
View file

@ -4,11 +4,29 @@ parsee*
parsee
*.swp
.*
data
data/*
data*
data*/*
Makefile
configure
gmon.out
tools/out
tools/out/*
ayaya/*
ayaya
#ctags
tags
# Whitelists
!etc/*
!etc/**
!.forgejo
!.forgejo/*
!.forgejo/**
!.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

84
CHANGELOG.md Normal file
View file

@ -0,0 +1,84 @@
# Parsee changelogs
This document holds changes logged between versions, ever
since `tomboyish-bridges-adventure`'s release.
Dates are to be written as DD/MM/YYYY. Please update the
changelog as you go, no one wants to keep track of every
commit done between releases.
## Release
*There is currently no full releases of Parsee*
## Beta
*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
up Parsee just a bit. MbedTLS support is still highly unstable.
#### New things
- Start dealing with some basic PEP and vCard-based avatar
support from both sides.
- Banning Parsee from a XMPP MUC effectively unlinks it from
the MUC.
- Start adding basic documentation to Parsee, through the
wiki page.
- Add MUC whitelists for plumbing, alongside a `whitelist` tool
- Add parameter for setting the max stanza size allowed, with
the default being the XMPP minimum of 10000 bytes.
- Allows experimental MbedTLS through a specific Cytoplasm
patch (though still unstable AND slow).
- Does basic work towards NetBSD support(especially with DEC Alpha)
- Start contextualising XMPP commands(all/Parsee admins/MUCs).
#### Bugfixes
- Adds more information to media events so that clients can
behave.
- Fixes issues where SIGPIPE can actually just kill Parsee.
- "Lone" XMPP messages no longer render weirdly on Element
Android's weird rendering.
- Start fixing bug where Parsee takes several seconds to send
a message coming from XMPP with MbedTLS(it is still slow and
unstable)
- Fix issue where the XMPP server would kill Parsee if avatars
are too large.
- Refactor some of the code to abstract sending stanzas down
the wire to a specific function, thus allowing us to check
for certain conditions more easily(for example, verifying the
size on the fly, or having extensions being able to postprocess
the stanza).
- Parsee now stops when a stream error is received, instead of
being in a limbo state.
- The format for links has been changed to be slighlty *less*
annoying.
#### Deprecated features
- The old `build.c` and `Makefile`s used for building are removed,
and replaced by the `configure.c` C file(/script via TCC).
### v0.1.0[tomboyish-bridges-adventure] <9/9/2024>
Nothing much to say, but this is the first alpha release
of Parsee. May occasionally deadlock.
#### New things
*NONE*
#### Bugfixes
*NONE*
#### Deprecated features
*NONE*

14
CONTRIBUTORS Normal file
View file

@ -0,0 +1,14 @@
# Lines starting with a '#' are ignored.
# This is a list of people who have contributed to Parsee.
# You *may* add some information about yourself after having made a change
# to the repository(or wiki!), with those fields:
# (N) - A nickname
# (L) - An approximate place of residence
# (X) - An XMPP URI
# (M) - A Matrix MXID
# (D) - A short description about yourself
(N): LDA
(L): France
(X): xmpp:lda@monocles.eu
(M): @fourier:ari.lt
(M): @fourier:ari.lt

View file

@ -1,7 +1,3 @@
Some dates for Parsee-related events. They mostly serve as LDA's TODOs with
a strict deadline:
- ~September 2024[star-of-hope]:
Get Parsee into the _Phantasmagoria of Bug View_ stage (essentially
v0.0.1 for a public testing) once I can afford `yama`, and start
bridging the Matrix room alongside a shiny XMPP MUC, bridged by
yours truly.
- Get star-of-hope out by November 8

View file

@ -1,6 +1,6 @@
For the files src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile, see COPYING.CC0
to be present.
For any other file in src/, see COPYING.AGPL as the primary license.
For the files src/include/Unistring.h, src/Unistr.h rc/Parsee/HMAC.c, src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile,
see COPYING.CC0.
For any other file in src/, see COPYING.AGPL as the primary license(AGPL-3.0-or-later)
As Parsee depends on Cytoplasm, its license is left here in COPYING.CYTO

View file

@ -1,89 +0,0 @@
# Makefile for building Parsee
# ================================
# TODO: Consider making something akin to a configure script that checks
# for dependencies, or maybe even use *autoconf* (the devil!)
# =========================== Parsee Flags =============================
# phantasmagoria - test runs without an actual code
CODE=phantasmagoria
NAME=Parsee
VERSION=0.0.0
REPOSITORY=$(shell git remote get-url origin)
# =========================== Compilation Flags ============================
CYTO_INC ?=/usr/local/include/ # Where Cytoplasm's include path is
# located.
CYTO_LIB ?=/usr/local/lib # Where's Cytoplasm's library is
# located.
PREFIX ?=/usr/local
SOURCE=src
OBJECT=build
AYAS=ayaya
ETC=etc
INCLUDES=src/include
CC=cc
CFLAGS=-I$(SOURCE) -I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -DCODE=\"$(CODE)\" -g -ggdb -Wall -Werror
LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -g -ggdb
AFLAGS=-C "$(ETC)/ayadoc/style.css" -p "$(NAME)"
BINARY=parsee
# ============================ Compilation =================================
SRC_FILES:=$(shell find $(SOURCE) -name '*.c')
OBJ_FILES:=${subst $(SOURCE)/,$(OBJECT)/,$(patsubst %.c, %.o, $(SRC_FILES))}
CPP_FILES:=$(shell find $(INCLUDES) -name '*.h')
AYA_FILES:=${subst $(INCLUDES)/,$(AYAS)/,$(patsubst %.h, %.html, $(CPP_FILES))}
all: binary utils
binary: $(OBJ_FILES)
$(CC) $(LDFLAGS) $(OBJ_FILES) -o $(BINARY)
clean:
rm -rf $(OBJECT) $(BINARY) $(AYAS)
$(OBJECT)/%.o: $(SOURCE)/%.c
@mkdir -p $(shell dirname "$@")
$(CC) -c $(CFLAGS) $< -o $@
utils:
(cd tools && make)
ayadoc: utils $(AYA_FILES)
$(AYAS)/%.html: $(INCLUDES)/%.h
@mkdir -p $(shell dirname "$@")
tools/out/aya $(AFLAGS) -i $< -o $@
# Installs everything.
install: binary utils ayadoc install_setup install_parsee install_tools install_aya install_man
@echo Installed $(NAME) to $(PREFIX)!
install_setup:
install -dm755 "$(PREFIX)/bin"
install -dm755 "$(PREFIX)/share/doc"
install -dm755 "$(PREFIX)/man"
install_parsee:
install -Dm755 "$(BINARY)" "$(PREFIX)/bin/$(BINARY)"
TOOLS:=$(shell find 'tools/out' -name '*')
ITOOL:=${subst tools/out/,$(PREFIX)/bin/,$(patsubst tools/out/%, tools/out/$(BINARY)-%, $(TOOLS))}
install_tools: $(ITOOL)
$(PREFIX)/bin/$(BINARY)-%: tools/out/%
install -Dm755 "$<" "$@"
IHTML:=${subst $(AYAS)/,$(PREFIX)/share/doc/$(BINARY)/,$(AYA_FILES)}
install_aya: $(IHTML)
$(PREFIX)/share/doc/$(BINARY)/%: $(AYAS)/%
install -Dm644 "$<" "$@"
MPAGE:=$(shell find 'etc/man' -name '*.*')
PPAGE:=${subst etc/man/,$(PREFIX)/man/,$(MPAGE)}
install_man: $(PPAGE)
$(PREFIX)/man/%: etc/man/%
install -Dm644 "$<" "$@"

View file

@ -1,39 +1,54 @@
# Parsee - the jealous XMPP<=>Matrix bridge
Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is NOT a drop-in replacment.
Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is
NOT a drop-in replacment.
Currently, it is *alpha* stage, which means that I wouldn't recommend using this in production,
as I can change anything, at any time, and it may behave strangely at times.
## Why?
### Naming
The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi), a
"*bridge* princess".
The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi),
a "*bridge* princess". The other name you actually can sometimes see explains itself, so I won't
be talking about it.
### Reasoning (personal to LDA)
I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister project to KappaChat,
this means that I can integrate Parsee with KappaChat however I wish it to be, which allows me to mess around with a
codebase I'm already familiar with.
A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*.
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, was literally where Parsee+XMPP ran for
a good chunk of Parsee's start.)
I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister
project to KappaChat, this means that I can integrate Parsee with KappaChat however I wish it
to be, which allows me to mess around with a codebase I'm already familiar with.
A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*,
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))
### "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.
## BUILDING
```sh
$ make # This generates a 'parsee' executable.
$ cd tools # If you want to build more tools
$ make && cd ..
$ make ayadoc # If you want to build HTML documentation
$ make [PREFIX=(install path)] install # To install Parsee.
$ cc configure.c -o configure # that or use tcc -run to consolidate these two steps.
$ ./configure # use -s if you want static Parsee+MbedTLS, use -s -l if LMDB is needed
$ make
$ make [PREFIX=...] install # run as root if on a protected dir like /usr
```
If there are any Cytoplasm-related build failures, you may want to check the Makefile to
change a few variables (you can set `CYTO_INC` and `CYTO_LIB`)
change a few variables (you can set `CYTO_INC` and `CYTO_LIB` for Cytoplasm's include and
library paths specifically.)
If you build with MbedTLS, please mind setting the `CYTO_TLS_CA` env to Parsee.
### DEPENDENCIES
Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm). Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but you can get away without those).
Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm).
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
@ -46,7 +61,8 @@ parsee-config \
-H 'blow.hole' \ # Matrix homeserver name
-J 'parsee.blow.hole' \ # XMPP component host, must be reachable
-s 'A very secure XMPP component secret' \
-p 5347
-p 5347 \
-M 65535 # Maximum stanza size. Stanzas larger than this from Parsee will be dropped to avoid stream closures. Leave this empty if you're unsure.
```
If everything goes well, it should generate a `parsee.json` file.
@ -59,31 +75,44 @@ returns with a landing page, then this side works. You can read it for some more
## DOCS
Currently, the main sources of documentation are the Ayadocs(for headers) and the manpages
(see `etc/man`)
(see `etc/man`).
## TODOS
- Add [libomemo](https://github.com/gkdr/libomemo) as an optional dependency.
## TODOS before 1.0 rolls around
- Make Parsee actually go *vroooooooooommmmmmm*.
- Make sure Parsee can easily run on just about any reasonable POSIX
system.
- Avoid making 'back-puppets' from Matrix as much as possible
- Extension support. I'd need to design a good system, and maybe do it
with either shared libraries(`dlopen`/`dlclose` on POSIX) or use a
language like Janet or Lua.
- Add [libomemo](https://github.com/gkdr/libomemo) or something as an optional dependency.
- It depends on more stuff anyways, and I don't want to weigh down the
dependency list of Parsee for that.
- Matrix's libolm is deprecated. They replaced it with a Rust version that
pulls in *way too many* dependencies, and that lacks a C binding. We may
put in the work of either forking off libolm or making a binding to KappaChat.
~~put in the work of either forking off libolm or~~ be making a binding with
KappaChat(when I get around to remaking UI :p).
- Josh did infact tell me that maybe C bindings may happen. I'd be
willing to help out, but IDK. In any case, this will at best be an
extension packagers may integrate properly.
- Get rid of the '?'-syntax and use another invalid Matrix char/valid XMPP char
('$'?) for escaped?
- PROPER FUCKING AVATARS
XEP-0084 IS THE WORST PIECE OF SHIT KNOWN TO MAN. If any Jabberbros want to
look at terrible code/XML and suggest things to have *proper* avatar support,
I'm all in.
- Consider making room/MUC admins/owners be able to plumb instead of it being
restricted to Parsee admins, with permission from MUC owners, too
- Limiting to admins may be a way to "control" consent for both, but this is
only if Parsee admins are good-willed, which we must assume such statment to
be false by default.
- Currently, MUC owners may kick Parsee out, with the effect of unlinking the
MUC.
- Look at XEPS-TBD.TXT for XEPs to be done
- Add a MUC server to Parsee, such that it may be able to hook onto it and therefore
support XMPP->Matrix bridging.
- Manage MUC DMs in a reasonable manner. Thanks `@freeoffers4u:matrix.org` for being
a fucking annoyance and DMing an old Parsee semi-anon user for no clear reason.
- Make Parsee cope with stream closures(i.e: XMPP server turning off) better. As of
now, it just kills itself when that happens, instead of trying to negociate a new
connection, which would be a better method that would actually fit Parsee's own
principles.
## DONATING/CONTRIBUTING
If you know things about XMPP or Matrix, yet aren't familiar with C99, or just
@ -100,7 +129,7 @@ You may also donate to [the LiberaPay](https://en.liberapay.com/Parsee), alongsi
currently maintaining Cytoplasm.
## IM chats
Please avoid asking for help/issues here. If you *really* want, you may just open
Please avoid asking for help/issues there. If you *really* want, you may just open
an issue and link it over it. Basic respect for others/not being an asshat is required.
(Also, these are temporary room aliases.)
- [#parsee:tedomum.net](https://matrix.to/#/%23parsee:tedomum.net)

View file

@ -1,6 +1,19 @@
XEPs current supported are in src/XMPPThread, at the IQ disco advertising.
Somewhat implemented XEPs:
v https://xmpp.org/extensions/xep-0050.html
Ad-hoc commands that bridge maintainers can deal with XMPP-style are
also a nice to have.
There are commands, but not a lot of them as of now, and localisation
is missing.
v https://xmpp.org/extensions/xep-0421.html
Using the occupant ID in semi-anonymous MUCs is a desirable property.
I dont know of a lot of places that don't use the occupant ID anymore
within Parsee.
v "also it [Bifrost] doesn't respect voice either"
As far as I am aware, works.
v https://xmpp.org/extensions/xep-0425.html
As mentionned in #2, moderation _needs_ to be done.
~ https://xmpp.org/extensions/xep-0085.html
Only XMPP->Matrix at the moment. Still need to figure out how to
get typing indicators as an AS.
@ -8,21 +21,14 @@ Somewhat implemented XEPs:
This allows reactions, which Matrix also has support to. The two
systems don't seem *too* restrictive on one-another (unlike some
IM platforms I won't mention), so this doesn't sound too bad to do
HALF-IMPLEMENTED: Removing reacts won't work.
TODO: Add support from Matrix.
~ https://xmpp.org/extensions/xep-0184.html
Only Matrix->XMPP as of now. Requesting data from Matrix ASes without
/sync seems like a non-option as of now, which _sucks_.
~ https://xmpp.org/extensions/xep-0050.html
Ad-hoc commands that bridge maintainers can deal with XMPP-style are
also a nice to have.
There are commands, but not a lot of them as of now, and localisation
is missing.
~ https://xmpp.org/extensions/xep-0421.html
Using the occupant ID in semi-anonymous MUCs is a desirable property.
I dont know of a lot of places that don't use the occupant ID anymore
within Parsee.
~ https://xmpp.org/extensions/xep-0425.html
As mentionned in #2, moderation _needs_ to be done.
~ https://xmpp.org/extensions/xep-0084.html
Avatar support would be extremely useful, if just a QoL improvment.
Matrix and XMPP both have support for these.
XEP-0084 is a pain in the ass to implement and seems generally just
For future XEPs:
- https://xmpp.org/extensions/xep-0449.html
@ -32,22 +38,16 @@ For future XEPs:
which is used along PEP, it seems, and meanwhile Matrix has ''support''
for packs too, tracking them is between "annoyance" and "yeah, no.".
ON STANDBY BECAUSE THESE HAVE BEEN TERRIBLE TO DEAL WITH AND WHO KEEPS WRITING
THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT:
x https://xmpp.org/extensions/xep-0084.html
Avatar support would be extremely useful, if just a QoL improvment.
Matrix and XMPP both have support for these.
XEP-0084 is a pain in the ass to implement and seems generally just
On Standby:
unreliable, however.
x https://xmpp.org/extensions/xep-0080.html
Can't think of a good analogy to these...
Not XEPs, but ideas that _needs_ to be added:
v "also it [Bifrost] doesn't respect voice either" -> Send a form on moderated
MUCs (which is standard, so its not too bad!). Currently WIP, and barely tested.
~ "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d
- Standalone/Static Parsee, ideally as small as it can be(if not as APE).
Happens on Matrix. I'll need to handle that on XMPP as well.
~ Standalone/Static Parsee, ideally as small as it can be(if not as APE).
- Kappa-like extension system(maybe bridging more than just Matrix-XMPP.)
- https://www.youtube.com/watch?v=InL414iDZmY

10
build.conf Normal file
View file

@ -0,0 +1,10 @@
CODE=lunar-rainbow
NAME=Parsee
VERSION=0.3.0
BINARY=parsee
SOURCE=src
INCLUDES=src/include
OBJECT=build
CC=cc
CFLAGS=-O3
PREFIX=/usr

727
configure.c Normal file
View file

@ -0,0 +1,727 @@
/* configure.c - Simple, POSIX, non-Cytoplasm utility to build out
* a Parsee Makefile.
* To run it, just build it with:
* cc configure.c -o configure && configure
* --------------------------------
* LICENSE: CC0
* Written-By: LDA [lda@freetards.xyz] [@fourier:ari.lt] */
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <libgen.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
static char *
string_rep_ext(char *in, char *ext1, char *ext2)
{
char *end;
size_t inLen;
if (!in || !ext1 || !ext2)
{
return NULL;
}
inLen = strlen(in);
end = inLen + in;
while (end >= in)
{
if (!strcmp(end, ext1))
{
size_t cpyLen = end - in;
size_t extLen = strlen(ext2);
char *ret = malloc(cpyLen + extLen + 1);
memcpy(ret, in, cpyLen);
memcpy(ret + cpyLen, ext2, extLen);
ret[cpyLen + extLen] = '\0';
return ret;
}
end--;
}
return NULL;
}
static char *
string_dup(char *in)
{
char *out;
size_t len;
if (!in)
{
return NULL;
}
len = strlen(in);
out = malloc(len + 1);
memcpy(out, in, len);
out[len] = '\0';
return out;
}
static char *
string_cat(char *in1, char *in2)
{
char *out;
size_t len1, len2;
if (!in1)
{
return string_dup(in2);
}
if (!in2)
{
return string_dup(in1);
}
len1 = strlen(in1);
len2 = strlen(in2);
out = malloc(len1 + len2 + 1);
memcpy(out, in1, len1);
memcpy(out + len1, in2, len2);
out[len1 + len2] = '\0';
return out;
}
typedef struct str_array {
char **values;
size_t quantity;
} str_array_t;
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;
return ret;
}
static void
str_array_free(str_array_t *arr)
{
size_t i;
if (!arr)
{
return;
}
for (i = 0; i < arr->quantity; i++)
{
if (arr->values[i]) free(arr->values[i]);
}
if (arr->values) free(arr->values);
free(arr);
}
static void
str_array_set(str_array_t *arr, size_t i, char *str)
{
if (!arr)
{
return;
}
if (i >= arr->quantity)
{
size_t size = (i+1) * sizeof(*arr->values);
size_t j;
arr->values = realloc(arr->values, size);
for (j = arr->quantity; j <= i; j++)
{
arr->values[j] = NULL;
}
arr->quantity = i + 1 ;
}
if (arr->values[i]) free(arr->values[i]);
arr->values[i] = string_dup(str);
}
static void
str_array_add(str_array_t *arr, char *str)
{
if (!arr)
{
return;
}
str_array_set(arr, arr->quantity, str);
}
static char *
str_array_get(str_array_t *arr, size_t i)
{
if (!arr)
{
return NULL;
}
return i < arr->quantity ? arr->values[i] : NULL;
}
static size_t
str_array_len(str_array_t *arr)
{
return arr ? arr->quantity : 0;
}
static char *
cmd_stdout(char *cmd)
{
FILE *f;
char *line = NULL;
size_t size;
int result;
if (!cmd)
{
goto failure;
}
if (!(f = popen(cmd, "r")))
{
goto failure;
}
getline(&line, &size, f);
result = pclose(f);
if (result < 0)
{
perror("pclose(3)");
goto failure;
}
else if (result)
{
fprintf(stderr, "command exited with status %d: %s\n", result, cmd);
goto failure;
}
return line;
failure:
free(line);
return NULL;
}
static char *
strchrn(char *s, char c)
{
s = strchr(s, c);
return s ? s+1 : NULL;
}
static str_array_t *
split(char *dir)
{
str_array_t *ret;
char *start;
if (!dir || strlen(dir) >= PATH_MAX - 1)
{
return NULL;
}
ret = str_array_create();
for (start = dir; start; start = strchrn(start, '/'))
{
char subtmp[PATH_MAX];
char *next = strchr(start, '/');
if (!next)
{
next = start + strlen(start);
}
memcpy(subtmp, start, next - start);
subtmp[next - start] = '\0';
str_array_add(ret, subtmp);
}
return ret;
}
static str_array_t *
collect_sources(char *dir, bool head, char *ext)
{
DIR *handle;
str_array_t *ret;
struct dirent *ent;
if (!dir)
{
return NULL;
}
ret = str_array_create();
handle = opendir(dir);
if (!handle)
{
printf("error: cannot open directory '%s'\n", dir);
return ret;
}
while ((ent = readdir(handle)))
{
char *name = ent->d_name;
if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
if (strlen(name) > strlen(ext) &&
!strcmp(name + strlen(name) - strlen(ext), ext))
{
char *d1 = string_cat(dir, "/");
char *na = string_cat(d1, name);
str_array_add(ret, na);
free(d1);
free(na);
continue;
}
{
char *d1 = string_cat(dir, "/");
char *d2 = string_cat(d1, name);
size_t i;
struct stat sb;
if (stat(d2, &sb))
{
fprintf(stderr, "stat(2) %s: %s\n", d2, strerror(errno));
free(d2);
free(d1);
}
else if (S_ISDIR(sb.st_mode))
{
str_array_t *sub = collect_sources(d2, false, ext);
for (i = 0; i < str_array_len(sub); i++)
{
char *file = str_array_get(sub, i);
str_array_add(ret, file);
}
str_array_free(sub);
}
free(d2);
free(d1);
}
}
closedir(handle);
return ret;
}
/* Builds the entirety of Parsee. */
static void
write_objects(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_images(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".png", ".o");
char *obj = string_cat("build", ofl + 3 + 1 + 5);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_executable(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_ayas(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".h", ".html");
char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
analyse_dependencies(FILE *makefile, char *src)
{
FILE *source = fopen(src, "r");
char *lineptr = NULL;
char *dn = strdup(src), *dirn;
size_t len = 0;
dirn = dirname(dn);
while (getline(&lineptr, &len, source) != -1)
{
char *corrptr = lineptr, *nl, *chevron, *quote, *cutoff;
char *basedir, *srcdir, *incdir, *tmp;
struct stat statinfo;
/* try to parse the line */
if ((nl = strchr(corrptr, '\n')))
{
*nl = '\0';
}
while (*corrptr && isblank(*corrptr))
{
corrptr++;
}
if (strncmp(corrptr, "#include \"", 10) &&
strncmp(corrptr, "#include <", 10))
{
continue;
}
corrptr += 10;
chevron = strchr(corrptr, '>');
chevron = chevron ? chevron : corrptr + strlen(corrptr);
quote = strchr(corrptr, '"');
quote = quote ? quote : corrptr + strlen(corrptr);
cutoff = chevron < quote ? chevron : quote;
*cutoff = '\0';
/* if we found something, try to resolve it */
tmp = string_cat(dirn, "/");
basedir = string_cat(tmp, corrptr);
free(tmp);
if (!stat(basedir, &statinfo))
{
fprintf(makefile, " %s", basedir);
free(basedir);
continue;
}
free(basedir);
srcdir = string_cat("src/", corrptr);
if (!stat(srcdir, &statinfo))
{
fprintf(makefile, " %s", srcdir);
free(srcdir);
continue;
}
free(srcdir);
incdir = string_cat("src/include/", corrptr);
if (!stat(incdir, &statinfo))
{
fprintf(makefile, " %s", incdir);
free(incdir);
continue;
}
free(incdir);
}
free(lineptr);
free(dn);
fclose(source);
}
static int
main_build(int argc, char *argv[])
{
FILE *makefile;
char *repo = cmd_stdout("git remote get-url origin");
size_t i;
bool with_static = false, with_lmdb = false;
int opt;
str_array_t *sources, *images, *utils, *aya;
if (repo)
{
char *lf = strchr(repo, '\n');
if (lf)
{
*lf = '\0';
}
}
else
{
repo = strdup("N/A");
}
while ((opt = getopt(argc, argv, "sl")) != -1)
{
switch (opt)
{
case 's':
with_static = true;
break;
case 'l':
with_lmdb = with_static;
with_static = true;
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");
fprintf(makefile, ".POSIX: \n");
fprintf(makefile, "include build.conf\n\n");
fprintf(makefile, "AYAYAS=ayaya\n");
fprintf(makefile, "CYTO_LIB=/usr/local/lib\n");
fprintf(makefile, "CYTO_INC=/usr/local/include\n");
fprintf(makefile, "ETC=etc\n\n");
fprintf(makefile, "all: utils binary ayadoc\n\n");
/* Create all objects */
sources = collect_sources("src", true, ".c");
images = collect_sources("etc/media", true, ".png");
fprintf(makefile, "binary:");
write_objects(makefile, sources);
write_images(makefile, images);
fprintf(makefile, "\n\t");
{
fprintf(makefile, "$(CC) -o $(BINARY)");
if (with_static)
{
fprintf(makefile, " -static");
}
fprintf(makefile, " -L $(CYTO_LIB)");
write_objects(makefile, sources);
write_images(makefile, images);
if (with_static)
{
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n");
}
/* Write rules for every source */
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
fprintf(makefile, "%s: %s", obj, src);
analyse_dependencies(makefile, src);
fprintf(makefile, "\n");
{
str_array_t *s = split(obj);
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
str_array_free(s);
fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) ");
fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" ");
fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" ");
fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" ");
fprintf(makefile, "-DREPOSITORY=\"\\\"%s\\\"\" ", repo);
fprintf(makefile, "$(CFLAGS) %s -o %s\n", src, obj);
}
free(ofl);
free(obj);
}
for (i = 0; i < str_array_len(images); i++)
{
char *src = str_array_get(images, i);
char *ofl = string_rep_ext(src, ".png", ".o");
char *obj = string_cat("build", ofl + 3 + 1 + 5);
char *cfl = string_cat("build", src + 3 + 1 + 5);
fprintf(makefile, "%s: %s\n", obj, src);
{
str_array_t *s = split(obj);
char *sym;
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
sym = j != -1 ? str_array_get(s, j): NULL;
sym = string_rep_ext(sym, ".o", "");
str_array_free(s);
fprintf(makefile, "\ttools/out/b64 %s 'media_%s' '%s.c'\n", src, sym, obj);
fprintf(makefile, "\t$(CC) -c %s.c -o %s\n", obj, obj);
free(sym);
}
free(obj);
free(ofl);
free(cfl);
}
/* Build utilities */
utils = collect_sources("tools", true, ".c");
fprintf(makefile, "utils:");
write_executable(makefile, utils);
fprintf(makefile, "\n");
for (i = 0; i < str_array_len(utils); i++)
{
char *src = str_array_get(utils, i);
char *ofl = string_rep_ext(src, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
fprintf(makefile, "%s: %s\n", obj, src);
{
fprintf(makefile, "\t@mkdir -p tools/out\n");
fprintf(makefile, "\t$(CC) -o %s", obj);
if (with_static)
{
fprintf(makefile, " -static");
}
fprintf(makefile, " %s", src);
fprintf(makefile, " -I $(CYTO_INC)");
fprintf(makefile, " -L $(CYTO_LIB)");
if (with_static)
{
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto");
fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(CFLAGS)\n");
}
free(ofl);
free(obj);
}
/* Build Aya */
aya = collect_sources("src/include", true, ".h");
fprintf(makefile, "ayadoc:");
write_ayas(makefile, aya);
fprintf(makefile, "\n");
for (i = 0; i < str_array_len(aya); i++)
{
char *src = str_array_get(aya, i);
char *ofl = string_rep_ext(src, ".h", ".html");
char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7);
fprintf(makefile, "%s: %s\n", obj, src);
{
str_array_t *s = split(obj);
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
str_array_free(s);
fprintf(makefile,
"\ttools/out/aya "
"-C etc/ayadoc/style.css "
"-p $(NAME) "
"-i %s -o %s\n", src, obj
);
}
free(ofl);
free(obj);
}
fprintf(makefile, "install-parsee: binary\n");
{
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n");
fprintf(makefile, "\tcp $(BINARY) $(PREFIX)/bin\n");
fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/$(BINARY)\n");
}
fprintf(makefile, "install-tools: utils\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n");
for (i = 0; i < str_array_len(utils); i++)
{
char *tool = str_array_get(utils, i);
char *ofl = string_rep_ext(tool, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
char *bna = basename(obj);
fprintf(makefile, "\tcp %s $(PREFIX)/bin/parsee-%s\n", obj, bna);
fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/parsee-%s\n", bna);
free(ofl);
free(obj);
}
fprintf(makefile, "install-aya: ayadoc\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/aya/$(BINARY)\n");
fprintf(makefile, "\tcp -R $(AYAYAS)/* $(PREFIX)/aya/$(BINARY)\n");
fprintf(makefile, "install-man:\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/share/man\n");
fprintf(makefile, "\tcp -R etc/man/* $(PREFIX)/share/man\n");
fprintf(makefile,
"install: "
"install-parsee "
"install-tools "
"install-aya "
"install-man\n"
);
fprintf(makefile, "clean:\n\trm -rf ayaya build $(BINARY) tools/out\n");
str_array_free(sources);
str_array_free(images);
str_array_free(utils);
str_array_free(aya);
fflush(makefile);
fclose(makefile);
free(repo);
return EXIT_SUCCESS;
}
int
main(int argc, char *argv[])
{
return main_build(argc, argv);
}

View file

@ -0,0 +1,40 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-adminify 1 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-adminify - bootstrap an admin to a new Parsee server
.SH SYNOPSIS
parsee-adminify
.B [DB path]
.B [user]
.SH DESCRIPTION
.I parsee-adminify
is a tool to add/list the Parsee administrator list. It currently only
allows you to see/add admins to it, using simplified globrules.
.SH OPTIONS
.TP
.B [config]
The configuration file used by the Parsee daemon.
.TP
.B [user]
If set, adds the glob
.I [user]
as a Parsee administrator. Otherwise, lists all administrators in
the Parsee instance.
.SH BUGS
.PP
None as I know of. If anyone notices any issues with this, please
let me know.
.SH LICENSE
Unlike Parsee,
.B parsee-adminify
is under the CC0/PD.
.SH SEE ALSO
.B parsee-config(1), parsee-aya(1), parsee(1)

40
etc/man/man1/parsee-aya.1 Normal file
View file

@ -0,0 +1,40 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-aya 1 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-aya - generate some nice Ayaya! documentation
.SH SYNOPSIS
parsee-aya
.B [-i HEADER]
.B [-o HTML]
.B [-C STYLESHEET]
.B [-p NAME]
.SH DESCRIPTION
.I parsee-aya
is a tool to generate Ayadocs(HTML documentation) from a formatted
header file. See a Parsee header file for an example of the inner usage.
.SH OPTIONS
.TP
.BR -i HEADER
The input header file to process out.
.TP
.BR -o HTML
The HTML file to write out the Ayadoc.
.TP
.BR -C STYLESHEET
A stylesheet file to use for Ayadocs.
.TP
.BR -p NAME
The project's name. If unavailable, defaults to Ayaya!
.SH LICENSE
Unlike Parsee,
.B parsee-aya
is under the CC0/PD.
.SH SEE ALSO
.B parsee-config(1), parsee-adminify(1), parsee(1)

View file

@ -0,0 +1,103 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-config 1 "Parsee Utility" "lunar-rainbow"
.SH NAME
parsee-config - generate a basic configuration file
.SH SYNOPSIS
parsee-config
.B [-H HOMESERVER_NAME]
.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]
.B [-S DATABASE size]
.SH DESCRIPTION
.I parsee-config
creates a basic configuration file to initialise a Parsee instance with the
given input flags, and makes a basic database. A basic example of
.I parsee-config
would be this(for a blow.hole server, with a xmpp.blow.hole JCP on Prosody,
and a 128MB LMDB backend)
.sp
.if n \{\
.RS 4
.\}
.nf
$ parsee-config \\
-d '/var/lib/parsee' \\
-m 'https://pmedia.blow.hole' \\
-H 'blow.hole' \\
-s 'The Dark Shared Secret' \\
-J 'xmpp.blow.hole' \\
-j 'localhost' \\
-S 128
.fi
.if n \{\
.RE
.\}
.sp
.SH OPTIONS
.TP
.BR -H HOMESERVER_NAME
.I HOMESERVER_NAME
points to the homeserver name, without delegation (which is autosensed).
For example, if you except Parsee users to be on
.IR @something:blow.hole
, then it should be set to
.IR blow.hole .
.TP
.BR -s SHARED_SECRET
.I SHARED_SECRET
is a shared secret known by Parsee and the XMPP component to authenticate.
.TP
.BR -M MAX_STANZA
.I MAX_STANZA
is the maximum stanza size accepted by the XMPP host. If it is less than 10000 bytes, then it shall be set to that limit(the standardised value).
.TP
.BR -m MEDIA_URL
.I MEDIA_URL
is an optional field used by Parsee as an address that points to Matrix
media. It must be publicly accessible (behind a reverse proxy to HTTP:7642)
.TP
.BR -J JABBER_HOST
.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
.I JABBER_HOST
to connect to
.I JABBER_HOST:JABBER_PORT
over TCP.
.TP
.BR -d DATABASE
The directory inwhich Parsee stores its database(LMDB/flat-file). Whenever the
database is flat or LMDB is dictated by the presence of the -S flag, and
whenever Cytoplasm has LMDB enabled.
.TP
.BR -S SIZE
If set, enables LMDB in Parsee, and sets its max DB size to
.BR SIZE MiB .
.SH LICENSE
Unlike Parsee,
.B parsee-config
is under the CC0/PD.
.SH SEE ALSO
.B parsee-aya(1), parsee-adminify(1), parsee(1)

118
etc/man/man1/parsee.1 Normal file
View file

@ -0,0 +1,118 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee 1 "Parsee Utility" "star-of-hope"
.SH NAME
parsee - the jealous XMPP-Matrix bridge
.SH SYNOPSIS
parsee
.B [-J num]
.B [-H num]
.B [-C file]
.B [-g]
.B [-v]
.B [-h]
.SH DESCRIPTION
Parsee is a
.B bridging program
, that is, it connects two chat protocols together (here, XMPP/Jabber,
and Matrix).
.PP
As such, a user on XMPP can communicate with Matrix users, and
vice-versa with it.
.PP
Currently, Parsee is under
.BI alpha .
This means that it is still subject to bugs, flaws, and may change in a
backwards-incompatible manner at any time.
.SH FIRST RUN
To start with a new install of Parsee(assuming you have a homeserver
with AS support and a XMPP server with JCP support, and that you know
the shared secret/domain for that component. Guides for that are
outside the scope of this manpage.), start by running
.B parsee-config(1)
with the flags provided in it, according to your configuration.
.PP
This should generate a
.I parsee.json
file, which is the configuration files for Parsee, and a directory where
the Parsee database may reside. You can then run
.I parsee -g
in that directory, which should generate a
.I parsee.yaml
file, this time with the generated AS configuration file, which you can
apply to your homeserver(how is implementation-dependent, but it is
generally a matter of modifying a configuration file to add the path
to such file).
.SH OPTIONS
.TP
.BR -J num
Set the number of threads used for XMPP stanza processing to
.I num\[char46]
.TP
.BR -H num
Set the number of threads used for HTTP request processing to
.I num\[char46]
.TP
.BR -C file
Sets the configuration JSON path to
.I file\[char46]
.PP
Defaults to
.I parsee.json
if none can be found.
.TP
.BR -g
Generates a
.I parsee.yaml
file to be used as an Application-Service entry within Matrix, and
exits.
.TP
.BR -v
Verbose logging of Parsee's behaviour. Recommended when testing Parsee
for bugs/hints.
.TP
.BR -h
Prints the command list with descriptions.
.SH BUGS
.PP
Sometimes Parsee will not respond to ^C requests properly, which
causes a system administrator to have to invoke SIGKILL.
.PP
Parsee seems to grow slowly in memory usage when messages are bridged
which, in the long run, could cause a memory error. All of the memory is
tracked, however, which means that this isn't exactly a leak.
.PP
Some important features still aren't implemented yet(e.g being able to join
a MUC from XMPP)
.SH CHATROOMS
You may talk about Parsee on these rooms(on Matrix and XMPP):
.RE
.IP xmpp:parsee@conference.monocles.eu?join
for XMPP, which is bridged along Matrix
.IP #parsee:tedomum.net
for Matrix, which is bridged along Parsee
.RS
.SH AUTHORS
." Contributors, feel free to put your names here in a PR, as long as
." it is acceptable
. PP
.BR LDA:
main maintainer of Parsee, accessible over XMPP at lda@freetards.xyz
and over Matrix as @fourier:ari.lt.
.SH LICENSE
Parsee is available under the AGPL3, but has some code under CC0/PD, and
some from Cytoplasm itself. Please see
.I https://git.kappach.at/lda/Parsee
for more information.
.SH SEE ALSO
.B parsee-config(1), parsee-adminify(1), parsee-aya(1)

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)

View file

@ -0,0 +1,50 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-cmd-syntax 7 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-cmd-syntax - Basic syntax information with Parsee Matrix commands
.SH DESCRIPTION
Parsee uses a specific syntax for commands, which is generally different
from regular bots, but closer to
.B dd(1) 's
syntax.
.PP
A command is formatted as so.
.sp
.if n \{\
.RS 4
.\}
.nf
.B ![NAME] arg1=val1 arg2='val2' arg3="val\(rs\(dq3"
.fi
.if n \{\
.RE
.\}
.sp
.PP
The
.B arg1=val1
syntax is to be used for simple values, with values containing no spaces.
.PP
The
.B NAME
attribute defines the command to be called, and the
.B arg1='val1' ,
and
.B arg2="val2"
syntax is to be used for simple values, with values containing spaces. If
the value needs to contain quotes, they may be escaped with
.B \(rs'
and
.B \(rs"
respectively.
.SH LICENSE
This document is under public domain, or CC0 if not allowed by local law.
.SH SEE ALSO
.B parsee(1)

5
etc/media/README Normal file
View file

@ -0,0 +1,5 @@
This directory is for any PNG media that needs to be integrated into Parsee.
Each file here is Base64-encoded then defined as a const char[] symbol with
the name being a function of the PNG filename: N(FILE.png)=media_FILE
NOTE: Medias should be about ~1024B MAX. Avoid to stride for anything larger.

BIN
etc/media/parsee_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
etc/media/unknown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

1
guix.scm Symbolic link
View file

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

971
src/AS.c
View file

@ -55,976 +55,5 @@ ASAuthenticateRequest(const ParseeConfig *data, HttpClientContext *ctx)
HttpRequestHeader(ctx, "Authorization", bearer);
Free(bearer);
}
bool
ASRegisterUser(const ParseeConfig *conf, char *user)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
HttpStatus status;
if (!conf || !user)
{
return false;
}
/* Create user. We don't actually care about the value as we can
* masquerade, as long as it exists. */
ctx = ParseeCreateRequest(
conf,
HTTP_POST, "/_matrix/client/v3/register"
);
json = HashMapCreate();
HashMapSet(json,"type",JsonValueString("m.login.application_service"));
user = ParseeGetLocal(user);
HashMapSet(json,"username",JsonValueString(user));
ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
return status == HTTP_OK;
}
void
ASPing(const ParseeConfig *conf)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path;
if (!conf)
{
return;
}
path = StrConcat(3,
"/_matrix/client/v1/appservice/",
"Parsee%20XMPP",
"/ping"
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
void
ASInvite(const ParseeConfig *conf, char *id, char *invited)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !invited)
{
return;
}
bridge = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/invite",
"?user_id=", bridge
);
Free(bridge);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(invited));
HashMapSet(json, "reason", JsonValueString("Pass over."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
void
ASBan(const ParseeConfig *conf, char *id, char *banned)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !banned)
{
return;
}
bridge = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/ban",
"?user_id=", bridge
);
Free(bridge);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(banned));
HashMapSet(json, "reason", JsonValueString("Parsee felt jealous."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
void
ASKick(const ParseeConfig *conf, char *id, char *banned)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !banned)
{
return;
}
bridge = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/kick",
"?user_id=", bridge
);
Free(bridge);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(banned));
HashMapSet(json, "reason", JsonValueString("Parsee felt jealous."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
char *
ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *ret;
if (!conf || !id)
{
return NULL;
}
if (!masquerade)
{
char *raw = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
masquerade = HttpUrlEncode(raw);
Free(raw);
}
else
{
masquerade = HttpUrlEncode(masquerade);
}
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/join/", id, "?",
"user_id=", masquerade
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(GrabString(json, 1, "room_id"));
JsonFree(json);
HttpClientContextFree(ctx);
Free(masquerade);
Free(id);
return ret;
}
void
ASLeave(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path;
if (!conf || !id)
{
return;
}
if (!masquerade)
{
char *raw = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
masquerade = HttpUrlEncode(raw);
Free(raw);
}
else
{
masquerade = HttpUrlEncode(masquerade);
}
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/leave?",
"user_id=", masquerade
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(masquerade);
Free(id);
}
void
ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state)
{
HttpClientContext *ctx = NULL;
char *path;
if (!conf || !id || !type || !mask || !state)
{
JsonFree(state);
return;
}
path = StrConcat(9,
"/_matrix/client/v3/rooms/", id, "/state/",
type, "/", key, "?", "user_id=", mask
);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, state);
HttpClientContextFree(ctx);
JsonFree(state);
}
char *
ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
{
HttpClientContext *ctx = NULL;
char *path;
char *txn, *ret;
HashMap *reply;
if (!conf || !id || !type || !user || !c)
{
JsonFree(c);
return NULL;
}
txn = StrRandom(16);
path = StrConcat(9,
"/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?",
"user_id=", user
);
Free(txn);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, c);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id")));
JsonFree(reply);
HttpClientContextFree(ctx);
JsonFree(c);
return ret;
}
char *
ASCreateRoom(const ParseeConfig *conf, char *by, char *alias)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *id;
if (!conf || !by)
{
return NULL;
}
path = StrConcat(3,
"/_matrix/client/v3/createRoom",
"?user_id=", by
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
if (alias)
{
char *trimmed = StrDuplicate(alias);
if (*alias == '#')
{
char *tmp, cb[2] = { 0 };
alias++;
Free(trimmed);
trimmed = NULL;
while (*alias && *alias != ':')
{
cb[0] = *alias;
tmp = trimmed;
trimmed = StrConcat(2, trimmed, cb);
Free(tmp);
alias ++;
}
}
HashMapSet(json, "room_alias_name", JsonValueString(trimmed));
Free(trimmed);
}
HashMapSet(json, "visibility", JsonValueString("public"));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id")));
HttpClientContextFree(ctx);
JsonFree(json);
return id;
}
char *
ASCreateDM(const ParseeConfig *conf, char *by, char *with)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *id;
if (!conf || !by || !with)
{
return NULL;
}
path = StrConcat(3,
"/_matrix/client/v3/createRoom",
"?user_id=", by
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
{
Array *invitees = ArrayCreate();
ArrayAdd(invitees, JsonValueString(with));
HashMapSet(json, "invite", JsonValueArray(invitees));
HashMapSet(json, "is_direct", JsonValueBoolean(true));
}
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id")));
HttpClientContextFree(ctx);
JsonFree(json);
return id;
}
void
ASSetAvatar(const ParseeConfig *conf, char *user, char *mxc)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!conf || !user || !mxc)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/profile/",
user, "/avatar_url", "?",
"user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "avatar_url", JsonValueString(mxc));
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
}
void
ASSetName(const ParseeConfig *conf, char *user, char *name)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!conf || !user || !name)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/profile/",
user, "/displayname", "?",
"user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "displayname", JsonValueString(name));
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
}
HashMap *
ASFind(const ParseeConfig *c, char *room, char *event)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path, *user;
if (!c || !room || !event)
{
return NULL;
}
user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base);
path = StrConcat(7,
"/_matrix/client/v3/rooms/",
room, "/event/", event, "?",
"user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(path);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
json = JsonDecode(HttpClientStream(ctx));
HttpClientContextFree(ctx);
Free(user);
return json;
}
char *
ASGetName(const ParseeConfig *c, char *room, char *user)
{
HttpClientContext *ctx;
HashMap *reply;
char *path, *ret;
char *u2 = user;
if (!c || !user)
{
return NULL;
}
if (!room)
{
user = HttpUrlEncode(user);
path = StrConcat(3,
"/_matrix/client/v3/profile/", user, "/displayname"
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "displayname"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
if (!ret)
{
ret = StrDuplicate(u2);
}
return ret;
}
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.member/", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "displayname"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
if (!ret)
{
ret = StrDuplicate(u2);
}
return ret;
}
HashMap *
ASGetPL(const ParseeConfig *c, char *room)
{
char *path;
HttpClientContext *ctx;
HashMap *reply;
if (!c || !room)
{
return NULL;
}
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.power_levels/", ""
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
HttpClientContextFree(ctx);
Free(path);
return reply;
}
void
ASSetPL(const ParseeConfig *conf, char *id, HashMap *m)
{
char *user;
if (!conf || !id || !m)
{
return;
}
user = StrConcat(4,
"@",conf->sender_localpart,
":",conf->server_base
);
ASSetState(conf, id, "m.room.power_levels", "", user, m);
Free(user);
}
char *
ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime)
{
char *size_str, *path, *ret, *user;
int i;
HttpClientContext *ctx;
HashMap *reply;
if (!c || !from)
{
return NULL;
}
size_str = StrInt(size);
user = StrConcat(4, "@",c->sender_localpart,":",c->server_base);
path = StrConcat(2,
"/_matrix/media/v3/upload?user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_POST, path);
ASAuthenticateRequest(c, ctx);
if (size)
{
HttpRequestHeader(ctx, "Content-Length", size_str);
}
if (mime)
{
HttpRequestHeader(ctx, "Content-Type", mime);
}
HttpRequestSendHeaders(ctx);
for (i = 0; i < size; i++)
{
int ch = StreamGetc(from);
if (ch == EOF)
{
break;
}
StreamPutc(HttpClientStream(ctx), ch);
}
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "content_uri"))
);
if (!ret)
{
JsonEncode(reply, StreamStdout(), JSON_PRETTY);
StreamFlush(StreamStdout());
}
HttpClientContextFree(ctx);
JsonFree(reply);
Free(size_str);
Free(path);
Free(user);
return ret;
}
char *
ASReupload(const ParseeConfig *c, char *from, char **mime)
{
Uri *uri;
HttpClientContext *ctx;
unsigned short port;
int size = 0, flags = HTTP_FLAG_NONE;
char *ret, *content_len;
if (!c || !from)
{
return NULL;
}
uri = UriParse(from);
if (!uri)
{
return NULL;
}
if (uri->port)
{
port = uri->port;
}
else if (StrEquals(uri->proto, "https"))
{
port = 443;
}
else
{
port = 80;
}
if (StrEquals(uri->proto, "https"))
{
flags |= HTTP_FLAG_TLS;
}
ctx = HttpRequest(
HTTP_GET, flags, port, uri->host, uri->path
);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
if (mime)
{
*mime = HashMapGet(HttpResponseHeaders(ctx), "content-type");
*mime = StrDuplicate(*mime);
}
content_len = HashMapGet(HttpResponseHeaders(ctx), "content-length");
if (content_len)
{
size = strtol(content_len, NULL, 10);
}
ret = ASUpload(c, HttpClientStream(ctx), size, mime ? *mime : NULL);
HttpClientContextFree(ctx);
UriFree(uri);
return ret;
}
void
ASType(const ParseeConfig *c, char *user, char *room, bool status)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!c || !user || !room)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/rooms/",
room, "/typing/", user,
"?user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "typing", JsonValueBoolean(status));
/* If someone types for 10 minutes straight, they got something weird man. */
HashMapSet(json, "timeout", JsonValueBoolean(10 MINUTES));
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(user);
}
void
ASPresence(const ParseeConfig *c, char *user, char *room, char *ev)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!c || !user || !room || !ev)
{
return;
}
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
ev = HttpUrlEncode(ev);
path = StrConcat(6,
"/_matrix/client/v3/rooms/",
room, "/receipt/m.read/", ev,
"?user_id=", user
);
json = HashMapCreate();
ctx = ParseeCreateRequest(c, HTTP_POST, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(user);
Free(room);
Free(ev);
}
HashMap *
ASGetUserConfig(const ParseeConfig *c, char *user, char *key)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!c || !key)
{
return NULL;
}
if (!user)
{
char *raw = StrConcat(4,
"@", c->sender_localpart,
":", c->server_base
);
user = HttpUrlEncode(raw);
Free(raw);
}
else
{
user = HttpUrlEncode(user);
}
path = StrConcat(7,
"/_matrix/client/v3/user/",
user, "/account_data/", key, "?",
"user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(path);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
json = JsonDecode(HttpClientStream(ctx));
HttpClientContextFree(ctx);
Free(user);
return json;
}
void
ASSetUserConfig(const ParseeConfig *c, char *user, char *key, HashMap *map)
{
HttpClientContext *ctx = NULL;
char *path;
if (!c || !key || !map)
{
JsonFree(map);
return;
}
if (!user)
{
char *raw = StrConcat(4,
"@", c->sender_localpart,
":", c->server_base
);
user = HttpUrlEncode(raw);
Free(raw);
}
else
{
user = HttpUrlEncode(user);
}
path = StrConcat(7,
"/_matrix/client/v3/user/",
user, "/account_data/", key, "?",
"user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, map);
HttpClientContextFree(ctx);
Free(user);
JsonFree(map);
return;
}
void
ASRedact(const ParseeConfig *c, char *room, char *user, char *e_id)
{
HttpClientContext *ctx = NULL;
HashMap *request;
char *path, *txn;
if (!c || !room || !e_id)
{
return;
}
if (!user)
{
char *raw = StrConcat(4,
"@", c->sender_localpart,
":", c->server_base
);
user = HttpUrlEncode(raw);
Free(raw);
}
else
{
user = HttpUrlEncode(user);
}
room = HttpUrlEncode(room);
e_id = HttpUrlEncode(e_id);
txn = StrRandom(16);
path = StrConcat(9,
"/_matrix/client/v3/rooms/",
room, "/redact/", e_id, "/", txn,
"?", "user_id=", user
);
request = HashMapCreate();
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, request);
JsonFree(request);
HttpClientContextFree(ctx);
Free(user);
Free(room);
Free(e_id);
Free(txn);
return;
}
void
ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, char *msg)
{
HttpClientContext *ctx = NULL;
HashMap *request;
char *path;
char *status_str = NULL;
if (!c || !user)
{
return;
}
switch (status)
{
case USER_STATUS_ONLINE: status_str = "online"; break;
case USER_STATUS_OFFLINE: status_str = "offline"; break;
case USER_STATUS_UNAVAILABLE: status_str = "unavailable"; break;
default: return;
}
user = HttpUrlEncode(user);
path = StrConcat(5,
"/_matrix/client/v3/presence/",user,"/status",
"?user_id=", user
);
Free(user);
request = HashMapCreate();
HashMapSet(request, "presence", JsonValueString(status_str));
if (msg)
{
HashMapSet(request, "status_msg", JsonValueString(msg));
}
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, request);
JsonFree(request);
HttpClientContextFree(ctx);
Free(path);
}

93
src/AS/Events.c Normal file
View file

@ -0,0 +1,93 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
HashMap *
ASFind(const ParseeConfig *c, char *room, char *event)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path, *user;
if (!c || !room || !event)
{
return NULL;
}
user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base);
path = StrConcat(7,
"/_matrix/client/v3/rooms/",
room, "/event/", event, "?",
"user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(path);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
json = JsonDecode(HttpClientStream(ctx));
HttpClientContextFree(ctx);
Free(user);
return json;
}
void
ASRedact(const ParseeConfig *c, char *room, char *user, char *e_id)
{
HttpClientContext *ctx = NULL;
HashMap *request;
char *path, *txn;
if (!c || !room || !e_id)
{
return;
}
if (!user)
{
char *raw = StrConcat(4,
"@", c->sender_localpart,
":", c->server_base
);
user = HttpUrlEncode(raw);
Free(raw);
}
else
{
user = HttpUrlEncode(user);
}
room = HttpUrlEncode(room);
e_id = HttpUrlEncode(e_id);
txn = StrRandom(16);
path = StrConcat(9,
"/_matrix/client/v3/rooms/",
room, "/redact/", e_id, "/", txn,
"?", "user_id=", user
);
request = HashMapCreate();
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, request);
JsonFree(request);
HttpClientContextFree(ctx);
Free(user);
Free(room);
Free(e_id);
Free(txn);
return;
}

120
src/AS/Indicators.c Normal file
View file

@ -0,0 +1,120 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
void
ASType(const ParseeConfig *c, char *user, char *room, bool status)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!c || !user || !room)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/rooms/",
room, "/typing/", user,
"?user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "typing", JsonValueBoolean(status));
/* If someone types for 5 minutes straight, they got something
* weird man. */
HashMapSet(json, "timeout", JsonValueInteger(5 MINUTES));
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(user);
}
void
ASPresence(const ParseeConfig *c, char *user, char *room, char *ev)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!c || !user || !room || !ev)
{
return;
}
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
ev = HttpUrlEncode(ev);
path = StrConcat(6,
"/_matrix/client/v3/rooms/",
room, "/receipt/m.read/", ev,
"?user_id=", user
);
json = HashMapCreate();
ctx = ParseeCreateRequest(c, HTTP_POST, path);
Free(path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(user);
Free(room);
Free(ev);
}
void
ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, char *msg)
{
HttpClientContext *ctx = NULL;
HashMap *request;
char *path;
char *status_str = NULL;
if (!c || !user)
{
return;
}
switch (status)
{
case USER_STATUS_ONLINE: status_str = "online"; break;
case USER_STATUS_OFFLINE: status_str = "offline"; break;
case USER_STATUS_UNAVAILABLE: status_str = "unavailable"; break;
default: return;
}
user = HttpUrlEncode(user);
path = StrConcat(5,
"/_matrix/client/v3/presence/",user,"/status",
"?user_id=", user
);
Free(user);
request = HashMapCreate();
HashMapSet(request, "presence", JsonValueString(status_str));
if (msg)
{
HashMapSet(request, "status_msg", JsonValueString(msg));
}
ctx = ParseeCreateRequest(c, HTTP_PUT, path);
ASAuthenticateRequest(c, ctx);
ParseeSetRequestJSON(ctx, request);
JsonFree(request);
HttpClientContextFree(ctx);
Free(path);
}

220
src/AS/Media.c Normal file
View file

@ -0,0 +1,220 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/Sha.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
char *
ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime)
{
char *size_str, *path, *ret, *user;
unsigned int i;
HttpClientContext *ctx;
HashMap *reply;
if (!c || !from)
{
return NULL;
}
size_str = StrInt(size);
user = StrConcat(4, "@",c->sender_localpart,":",c->server_base);
path = StrConcat(2,
"/_matrix/media/v3/upload?user_id=", user
);
ctx = ParseeCreateRequest(c, HTTP_POST, path);
ASAuthenticateRequest(c, ctx);
if (size)
{
HttpRequestHeader(ctx, "Content-Length", size_str);
}
if (mime)
{
HttpRequestHeader(ctx, "Content-Type", mime);
}
HttpRequestSendHeaders(ctx);
for (i = 0; i < size; i++)
{
int ch = StreamGetc(from);
if (ch == EOF)
{
break;
}
StreamPutc(HttpClientStream(ctx), ch);
}
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "content_uri"))
);
if (!ret)
{
JsonEncode(reply, StreamStdout(), JSON_PRETTY);
StreamFlush(StreamStdout());
}
HttpClientContextFree(ctx);
JsonFree(reply);
Free(size_str);
Free(path);
Free(user);
return ret;
}
char *
ASReupload(const ParseeConfig *c, char *from, char **mime)
{
Uri *uri;
HttpClientContext *ctx;
unsigned short port;
int size = 0, flags = HTTP_FLAG_NONE;
char *ret, *content_len;
if (!c || !from)
{
return NULL;
}
uri = UriParse(from);
if (!uri)
{
return NULL;
}
if (uri->port)
{
port = uri->port;
}
else if (StrEquals(uri->proto, "https"))
{
port = 443;
}
else
{
port = 80;
}
if (StrEquals(uri->proto, "https"))
{
flags |= HTTP_FLAG_TLS;
}
ctx = HttpRequest(
HTTP_GET, flags, port, uri->host, uri->path
);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
if (mime)
{
*mime = HashMapGet(HttpResponseHeaders(ctx), "content-type");
*mime = StrDuplicate(*mime);
}
content_len = HashMapGet(HttpResponseHeaders(ctx), "content-length");
if (content_len)
{
size = strtol(content_len, NULL, 10);
}
ret = ASUpload(c, HttpClientStream(ctx), size, mime ? *mime : NULL);
HttpClientContextFree(ctx);
UriFree(uri);
return ret;
}
bool
ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
unsigned char *sha1;
size_t len;
if (!c || !mxc || !mime || !sha)
{
return false;
}
*mime = NULL;
*sha = NULL;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, &len));
StreamCopy(stream, fake);
StreamClose(fake);
sha1 = Sha1Raw((unsigned char *) buf, len);
free(buf);
*sha = ShaToHex(sha1, HASH_SHA1);
Free(sha1);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}
bool
ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
if (!c || !mxc || !mime || !out || !len)
{
return false;
}
*mime = NULL;
*out = NULL;
*len = 0;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, len));
StreamCopy(stream, fake);
StreamClose(fake);
*out = Malloc(*len);
memcpy(*out, buf, *len);
free(buf);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}

42
src/AS/Ping.c Normal file
View file

@ -0,0 +1,42 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
bool
ASPing(const ParseeConfig *conf)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path;
bool ret;
if (!conf)
{
return false;
}
path = StrConcat(3,
"/_matrix/client/v1/appservice/",
"Parsee%20XMPP",
"/ping"
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ret = ParseeSetRequestJSON(ctx, json) == HTTP_OK;
HttpClientContextFree(ctx);
JsonFree(json);
return ret;
}

206
src/AS/Profile.c Normal file
View file

@ -0,0 +1,206 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
void
ASSetAvatar(const ParseeConfig *conf, char *user, char *mxc)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!conf || !user || !mxc)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/profile/",
user, "/avatar_url", "?",
"user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "avatar_url", JsonValueString(mxc));
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
}
void
ASSetName(const ParseeConfig *conf, char *user, char *name)
{
HttpClientContext *ctx = NULL;
HashMap *json;
char *path;
if (!conf || !user || !name)
{
return;
}
user = HttpUrlEncode(user);
path = StrConcat(6,
"/_matrix/client/v3/profile/",
user, "/displayname", "?",
"user_id=", user
);
json = HashMapCreate();
HashMapSet(json, "displayname", JsonValueString(name));
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
}
char *
ASGetName(const ParseeConfig *c, char *room, char *user)
{
HttpClientContext *ctx;
HashMap *reply;
char *path, *ret;
char *u2 = user;
if (!c || !user)
{
return NULL;
}
if (!room)
{
user = HttpUrlEncode(user);
path = StrConcat(3,
"/_matrix/client/v3/profile/", user, "/displayname"
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "displayname"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
if (!ret)
{
ret = StrDuplicate(u2);
}
return ret;
}
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.member/", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "displayname"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
if (!ret)
{
ret = StrDuplicate(u2);
}
return ret;
}
char *
ASGetAvatar(const ParseeConfig *c, char *room, char *user)
{
HttpClientContext *ctx;
HashMap *reply;
char *path = NULL, *ret = NULL;
char *u2 = user;
if (!c || !user)
{
return NULL;
}
if (room)
{
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.member/", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
user = u2;
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from room, got %s", ret);
}
if (!ret)
{
user = HttpUrlEncode(user);
path = StrConcat(3,
"/_matrix/client/v3/profile/", user, "/avatar_url"
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
user = u2;
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from profile, got %s", ret);
}
return ret;
}

46
src/AS/Register.c Normal file
View file

@ -0,0 +1,46 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
bool
ASRegisterUser(const ParseeConfig *conf, char *user)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
HttpStatus status;
if (!conf || !user)
{
return false;
}
/* Create user. We don't actually care about the value as we can
* masquerade, as long as it exists. */
ctx = ParseeCreateRequest(
conf,
HTTP_POST, "/_matrix/client/v3/register"
);
json = HashMapCreate();
HashMapSet(json,"type",JsonValueString("m.login.application_service"));
user = ParseeGetLocal(user);
HashMapSet(json,"username",JsonValueString(user));
ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
return status == HTTP_OK;
}

82
src/AS/Relations.c Normal file
View file

@ -0,0 +1,82 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
Array *
ASGetRelations(const ParseeConfig *c, size_t n, char *room, char *event, char *type)
{
HttpClientContext *ctx = NULL;
Array *ret, *chunk;
HashMap *json = NULL;
char *path;
char *user;
size_t i;
if (!c || !n || !room || !event)
{
return NULL;
}
user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base);
if (type)
{
path = StrConcat(8,
"/_matrix/client/v1/rooms/", room,
"/relations/", event, "/", type,
"?user_id=", user
);
}
else
{
path = StrConcat(6,
"/_matrix/client/v1/rooms/", room,
"/relations/", event,
"?user_id=", user
);
}
Free(user);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(path);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
json = JsonDecode(HttpClientStream(ctx));
ret = ArrayCreate();
chunk = GrabArray(json, 1, "chunk");
for (i = 0; i < ArraySize(chunk); i++)
{
HashMap *obj = JsonValueAsObject(ArrayGet(chunk, i));
ArrayAdd(ret, JsonDuplicate(obj));
}
HttpClientContextFree(ctx);
JsonFree(json);
return ret;
}
void
ASFreeRelations(Array *relations)
{
size_t i;
if (!relations)
{
return;
}
for (i = 0; i < ArraySize(relations); i++)
{
JsonFree(ArrayGet(relations, i));
}
ArrayFree(relations);
}

366
src/AS/Room.c Normal file
View file

@ -0,0 +1,366 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
void
ASInvite(const ParseeConfig *conf, char *id, char *invited)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !invited)
{
return;
}
bridge = StrConcat(4,
"@", 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,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(invited));
HashMapSet(json, "reason", JsonValueString("Pass over."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
void
ASBan(const ParseeConfig *conf, char *id, char *banned)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !banned)
{
return;
}
bridge = StrConcat(4,
"@", 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,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(banned));
HashMapSet(json, "reason", JsonValueString(NAME " felt jealous."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
void
ASKick(const ParseeConfig *conf, char *id, char *banned)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *bridge;
if (!conf || !id || !banned)
{
return;
}
bridge = StrConcat(4,
"@", 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,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
HashMapSet(json, "user_id", JsonValueString(banned));
HashMapSet(json, "reason", JsonValueString(NAME " felt jealous."));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
char *
ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *ret, *serv;
int status;
if (!conf || !id)
{
return NULL;
}
if (!masquerade)
{
char *raw = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
masquerade = HttpUrlEncode(raw);
Free(raw);
}
else
{
masquerade = HttpUrlEncode(masquerade);
}
serv = strchr(id, ':');
if (serv)
{
serv = serv + 1;
}
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/join/", id, "?",
"user_id=", masquerade
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(GrabString(json, 1, "room_id"));
JsonFree(json);
HttpClientContextFree(ctx);
Free(masquerade);
Free(id);
(void) serv; // TODO
return ret;
}
void
ASLeave(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path;
if (!conf || !id)
{
return;
}
if (!masquerade)
{
char *raw = StrConcat(4,
"@", conf->sender_localpart,
":", conf->server_base
);
masquerade = HttpUrlEncode(raw);
Free(raw);
}
else
{
masquerade = HttpUrlEncode(masquerade);
}
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/leave?",
"user_id=", masquerade
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
HttpClientContextFree(ctx);
Free(masquerade);
Free(id);
}
char *
ASCreateRoom(const ParseeConfig *conf, char *by, char *alias)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *id;
if (!conf || !by)
{
return NULL;
}
path = StrConcat(3,
"/_matrix/client/v3/createRoom",
"?user_id=", by
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
if (alias)
{
char *trimmed = StrDuplicate(alias);
if (*alias == '#')
{
char *tmp, cb[2] = { 0 };
alias++;
Free(trimmed);
trimmed = NULL;
while (*alias && *alias != ':')
{
cb[0] = *alias;
tmp = trimmed;
trimmed = StrConcat(2, trimmed, cb);
Free(tmp);
alias ++;
}
}
HashMapSet(json, "room_alias_name", JsonValueString(trimmed));
Free(trimmed);
}
HashMapSet(json, "visibility", JsonValueString("public"));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id")));
HttpClientContextFree(ctx);
JsonFree(json);
return id;
}
char *
ASCreateDM(const ParseeConfig *conf, char *by, char *with)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *id;
if (!conf || !by || !with)
{
return NULL;
}
path = StrConcat(3,
"/_matrix/client/v3/createRoom",
"?user_id=", by
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
{
Array *invitees = ArrayCreate();
ArrayAdd(invitees, JsonValueString(with));
HashMapSet(json, "invite", JsonValueArray(invitees));
HashMapSet(json, "is_direct", JsonValueBoolean(true));
}
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id")));
HttpClientContextFree(ctx);
JsonFree(json);
return id;
}
HashMap *
ASGetPL(const ParseeConfig *c, char *room)
{
char *path;
HttpClientContext *ctx;
HashMap *reply;
if (!c || !room)
{
return NULL;
}
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.power_levels/", ""
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
HttpClientContextFree(ctx);
Free(path);
return reply;
}
void
ASSetPL(const ParseeConfig *conf, char *id, HashMap *m)
{
char *user;
if (!conf || !id || !m)
{
return;
}
user = StrConcat(4,
"@",conf->sender_localpart,
":",conf->server_base
);
ASSetState(conf, id, "m.room.power_levels", "", user, m);
Free(user);
}

79
src/AS/Send.c Normal file
View file

@ -0,0 +1,79 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <Matrix.h>
static char *
TSToStr(uint64_t ts)
{
size_t len;
char *str;
len = snprintf(NULL, 0, "%"PRIu64, ts);
str = Malloc(len+1);
snprintf(str, len+1, "%"PRIu64, ts);
return str;
}
char *
ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, uint64_t ts)
{
HttpClientContext *ctx = NULL;
char *path;
char *txn, *ret;
char *ts_str;
HttpStatus status;
if (!ret)
{
Log(LOG_ERR, "%", ret);
}
HashMap *reply;
if (!conf || !id || !type || !user || !c)
{
JsonFree(c);
return NULL;
}
if (!ts)
{
ts = UtilTsMillis();
}
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);
status = ParseeSetRequestJSON(ctx, c);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id")));
JsonFree(reply);
HttpClientContextFree(ctx);
JsonFree(c);
return ret;
}

37
src/AS/State.c Normal file
View file

@ -0,0 +1,37 @@
#include <AS.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <string.h>
#include <stdlib.h>
#include <Matrix.h>
void
ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state)
{
HttpClientContext *ctx = NULL;
char *path;
if (!conf || !id || !type || !mask || !state)
{
JsonFree(state);
return;
}
path = StrConcat(9,
"/_matrix/client/v3/rooms/", id, "/state/",
type, "/", key, "?", "user_id=", mask
);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, state);
HttpClientContextFree(ctx);
JsonFree(state);
}

View file

@ -25,7 +25,7 @@ CommandParse(char *cmd)
}
end_data = strchr(cmd, ' ');
if (!end_data)
if (!end_data || (cmd > end_data))
{
ret = Malloc(sizeof(*ret));
ret->command = StrDuplicate(cmd);
@ -49,11 +49,11 @@ CommandParse(char *cmd)
char c = *cur;
char *tmp;
bool type;
char char_type;
char char_type = '\0';
switch (state)
{
case STATE_WHITE:
if (!isblank(c))
if (!isblank((int) c))
{
state = STATE_NAME;
namestart = cur;
@ -84,7 +84,7 @@ CommandParse(char *cmd)
{
char c = *cur;
char cb[2] = { c, '\0' };
if ((type && c == char_type) || (!type && isblank(c)))
if ((type && c == char_type) || (!type && isblank((int) c)))
{
break;
}

View file

@ -15,35 +15,47 @@ CommandCreateRouter(void)
void
CommandAddCommand(CommandRouter *rter, char *c, CommandRoute rte)
{
CommandRoute *indirect;
if (!rter || !c || !rte)
{
return;
}
HashMapSet(rter->routes, c, rte);
/* Little dirty trick to force C99 into submission, and since
* some architectures may separate data/code. Still don't like it... */
indirect = Malloc(sizeof(rte));
*indirect = rte;
HashMapSet(rter->routes, c, (void *) indirect);
}
void
RouteCommand(CommandRouter *rter, Command *cmd, void *d)
{
CommandRoute route;
CommandRoute *route;
if (!rter || !cmd)
{
return;
}
route = HashMapGet(rter->routes, cmd->command);
if (route)
if (route && *route)
{
route(cmd, d);
(*route)(cmd, d);
}
}
void
CommandFreeRouter(CommandRouter *rter)
{
char *key;
CommandRoute *val;
if (!rter)
{
return;
}
while (HashMapIterate(rter->routes, &key, (void **) &val))
{
Free(val);
}
HashMapFree(rter->routes);
Free(rter);
}

View file

@ -22,11 +22,28 @@ CommandHead(CmdBanUser, cmd, argp)
BotDestroy();
return;
}
ASBan(data->config, room, user);
ReplySprintf("Banning %s from '%s'...",
user, room
);
ASBan(data->config, room, user);
ReplySprintf("Banning %s from '%s'...", user, room);
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();
}

View file

@ -25,4 +25,6 @@ CommandHead(CmdHelp, cmd, argp)
);
ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*");
BotDestroy();
(void) cmd;
}

View file

@ -32,4 +32,6 @@ CommandHead(CmdListBans, cmd, argp)
DbUnlock(data->db, listed);
BotDestroy();
(void) cmd;
}

View file

@ -24,7 +24,8 @@ CommandHead(CmdPlumb, cmd, argp)
BotRequired(room);
/* Check MUC viability */
if (ParseeManageBan(args->data, muc, NULL))
if (ParseeManageBan(args->data, muc, NULL) ||
!ParseeIsMUCWhitelisted(args->data, muc))
{
ReplySprintf("MUC '%s' is not allowed on this bridge.", muc);
goto end;
@ -62,7 +63,7 @@ CommandHead(CmdPlumb, cmd, argp)
if (chat_id)
{
char *rev = StrConcat(2, muc, "/parsee");
XMPPJoinMUC(args->data->jabber, "parsee", rev);
XMPPJoinMUC(args->data->jabber, "parsee", rev, NULL, -1, false);
Free(rev);
}

View file

@ -24,7 +24,6 @@ CommandHead(CmdStats, cmd, argp)
BotInitialise();
/* TODO: Separate these into different "categories" */
ReplySprintf("Information for %s v%s (Cytoplasm %s)",
NAME, VERSION, CytoplasmGetVersionStr()
);
@ -41,4 +40,6 @@ CommandHead(CmdStats, cmd, argp)
ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*");
BotDestroy();
(void) cmd;
}

View file

@ -9,43 +9,60 @@
#include <stdlib.h>
static bool
Grab(ParseeData *data, Command *cmd, char **muc, char **chat_id, char **room)
{
if (HashMapGet(cmd->arguments, "muc"))
{
*muc = HashMapGet(cmd->arguments, "muc");
*chat_id = ParseeGetFromMUCID(data, *muc);
*room = ParseeGetRoomID(data, *chat_id);
if (!chat_id || !room)
{
return false;
}
return true;
}
else if (HashMapGet(cmd->arguments, "room"))
{
*room = HashMapGet(cmd->arguments, "room");
*chat_id = ParseeGetFromRoomID(data, *room);
*muc = ParseeGetMUCID(data, *chat_id);
if (!chat_id || !muc)
{
return false;
}
return true;
}
return false;
}
CommandHead(CmdUnlinkMUC, cmd, argp)
{
ParseeCmdArg *args = argp;
ParseeData *data = args->data;
HashMap *json, *event = args->event, *mucs;
DbRef *ref;
HashMap *event = args->event;
char *muc = NULL, *chat_id = NULL, *room = NULL;
BotInitialise();
muc = HashMapGet(cmd->arguments, "muc");
if (!muc)
if (!Grab(data, cmd, &muc, &chat_id, &room))
{
ReplyBasic("`muc` field REQUIRED.");
ReplyBasic("`muc`|`room` REQUIRED");
goto end;
}
ref = DbLock(data->db, 1, "chats");
json = DbJson(ref);
chat_id = StrDuplicate(GrabString(json, 2, "mucs", muc));
chat_id = ParseeGetFromMUCID(data, muc);
room = ParseeGetRoomID(data, chat_id);
if (!chat_id)
{
ReplySprintf("No internal mapping to '%s'.", muc);
goto end;
}
mucs = GrabObject(json, 1, "mucs");
JsonValueFree(HashMapDelete(mucs, muc));
DbUnlock(data->db, ref);
room = ParseeGetRoomID(data, chat_id);
ref = DbLock(data->db, 1, "chats");
json = DbJson(ref);
mucs = GrabObject(json, 1, "rooms");
JsonValueFree(HashMapDelete(mucs, room));
DbUnlock(data->db, ref);
DbDelete(data->db, 2, "chats", chat_id);
ParseeUnlinkRoom(data, chat_id);
/* TODO: Do it automatically, if *not plumbed* */
ReplySprintf("The MUC %s is now *unlinked*.", muc);

View file

@ -37,12 +37,10 @@ MatrixCreateMessage(char *body)
HashMapSet(map, "msgtype", JsonValueString("m.text"));
HashMapSet(map, "body", JsonValueString(body));
{
/* TODO */
XEP393Element *e = XEP393(body);
text = XEP393ToXMLString(e);
XEP393FreeElement(e);
HashMapSet(map, "formatted_body", JsonValueString(text));
HashMapSet(map, "format", JsonValueString("org.matrix.custom.html"));
Free(text);
@ -81,7 +79,7 @@ MatrixCreateNickChange(char *nick)
return map;
}
HashMap *
MatrixCreateMedia(char *mxc, char *body, char *mime)
MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info)
{
HashMap *map;
char *mime_type = NULL, *matrix_type = NULL;
@ -93,9 +91,10 @@ MatrixCreateMedia(char *mxc, char *body, char *mime)
matrix_type = "m.file";
if (mime)
{
size_t i;
size_t i, len;
mime_type = StrDuplicate(mime);
for (i = 0; i < strlen(mime); i++)
len = strlen(mime);
for (i = 0; i < len; i++)
{
if (mime_type[i] == '/')
{
@ -121,6 +120,12 @@ MatrixCreateMedia(char *mxc, char *body, char *mime)
}
map = HashMapCreate();
JsonSet(map, JsonValueString(mime), 2, "info", "mimetype");
if (info && info->width && info->height)
{
JsonSet(map, JsonValueInteger(info->width), 2, "info", "w");
JsonSet(map, JsonValueInteger(info->height), 2, "info", "h");
}
HashMapSet(map, "msgtype", JsonValueString(matrix_type));
HashMapSet(map, "mimetype", JsonValueString(mime));
HashMapSet(map, "body", JsonValueString(body));
@ -182,6 +187,18 @@ MatrixCreateReplace(char *event, char *body)
HashMapSet(map, "m.new_content", JsonValueObject(new));
HashMapSet(map, "m.relates_to", JsonValueObject(rel));
{
XEP393Element *e = XEP393(body);
char *text = XEP393ToXMLString(e);
XEP393FreeElement(e);
HashMapSet(map, "formatted_body", JsonValueString(text));
HashMapSet(map, "format", JsonValueString("org.matrix.custom.html"));
HashMapSet(new, "formatted_body", JsonValueString(text));
HashMapSet(new, "format", JsonValueString("org.matrix.custom.html"));
Free(text);
}
return map;
}
HashMap *

68
src/FileInfo.c Normal file
View file

@ -0,0 +1,68 @@
#include <FileInfo.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <string.h>
#include <stdlib.h>
static int
GetField(XMLElement *elem)
{
XMLElement *child;
if (!elem || ArraySize(elem->children) != 1)
{
return 0;
}
child = ArrayGet(elem->children, 0);
return strtol(child->data, NULL, 10);
}
FileInfo *
FileInfoFromXMPP(XMLElement *stanza)
{
FileInfo *info;
XMLElement *reference, *sims, *file;
if (!stanza)
{
return NULL;
}
reference = XMLookForTKV(stanza,
"reference", "xmlns", "urn:xmpp:reference:0"
);
sims = XMLookForTKV(reference,
"media-sharing", "xmlns", "urn:xmpp:sims:1"
);
file = XMLookForTKV(sims,
"file", "xmlns", "urn:xmpp:jingle:apps:file-transfer:5"
);
/* TODO: We'll definitely need MIME types to do things like
* WebXDC */
if (!file)
{
return NULL;
}
info = Malloc(sizeof(*info));
info->width = GetField(XMLookForUnique(file, "width"));
info->height = GetField(XMLookForUnique(file, "height"));
info->size = GetField(XMLookForUnique(file, "size"));
return info;
}
void
FileInfoFree(FileInfo *info)
{
if (!info)
{
return;
}
Free(info);
}

View file

@ -19,7 +19,6 @@ GlobMatches(char *rule, char *string)
switch (c1)
{
case '*':
/* TODO */
while ((c2 = *string) && (c2 != next))
{
string++;

View file

@ -29,19 +29,18 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
arg.stream = stream;
Log(LOG_NOTICE, "%s %s",
Log(LOG_DEBUG, "%s %s",
HttpRequestMethodToString(HttpRequestMethodGet(ctx)),
path
);
if (!HttpRouterRoute(data->router, path, &arg, (void **) &response))
{
Log(LOG_NOTICE, "Couldn't route %s", path);
Log(LOG_DEBUG, "Couldn't route %s", path);
HttpResponseStatus(ctx, HTTP_NOT_FOUND);
JsonFree(response);
response = MatrixCreateError("M_NOT_FOUND", "Route not found.");
/* TODO: Set a thing */
}
/* Whatever, we routed a thing. */
@ -56,8 +55,11 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
HttpSendHeaders(ctx);
JsonEncode(response, stream, JSON_DEFAULT);
JsonFree(response);
return;
}
Log(LOG_DEBUG, "%s %s (%d)",
HttpRequestMethodToString(HttpRequestMethodGet(ctx)),
path, HttpResponseStatusGet(ctx)
);
}
HttpClientContext *
@ -71,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

@ -1,5 +1,6 @@
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/Platform.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Cron.h>
@ -12,6 +13,7 @@
#include <signal.h>
#include <stdlib.h>
#include <StanzaBuilder.h>
#include <Parsee.h>
#include <XMPP.h>
#include <AS.h>
@ -27,6 +29,79 @@ ParseeUptime(void)
return UtilTsMillis() - start;
}
static const Argument arguments[] =
{
#define Arg(c, req, vdesc, desc) \
{ \
.end = false, \
.argument = c, .value_req = req, \
.value_descr = vdesc, \
.description = desc \
},
#define EndOfArgs { .end = true }
Arg('H', true, "number(=8)", "Sets the number of HTTP threads")
Arg('J', true, "number(=8)", "Sets the number of XMPP threads")
Arg('C', true, "file(='parsee.json')", "Sets the JSON config to use")
Arg('g', false, NULL,
"Generates a parsee.yaml AS file before exiting")
Arg('v', false, NULL,
"Forces Parsee to print in a more verbose fashion "
"(-vvv prints stanzas to stderr)")
Arg('h', false, NULL,
"Generates an help screen(this one!)")
EndOfArgs
#undef EndOfArgs
#undef Argument
};
void
ParseeCheckMatrix(void *datp)
{
static volatile uint64_t streak = 0;
ParseeData *data = datp;
if (data->config->accept_pings && !ASPing(data->config))
{
Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host);
if (++streak == 10)
{
DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats");
HashMap *json = DbJson(ref);
HashMap *mucs = GrabObject(json, 1, "mucs");
char *muc;
void *ignored;
/* 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;
}
streak = 0;
}
int
Main(Array *args, HashMap *env)
{
@ -35,6 +110,11 @@ Main(Array *args, HashMap *env)
Stream *yaml;
Cron *cron = NULL;
char *configuration = "parsee.json";
int xmpp = 8;
int http = 8;
int verbose = 0;
start = UtilTsMillis();
memset(&conf, 0, sizeof(conf));
@ -42,24 +122,32 @@ Main(Array *args, HashMap *env)
"%s - v%s[%s] (Cytoplasm %s)",
NAME, VERSION, CODE, CytoplasmGetVersionStr()
);
ParseePrintASCII();
Log(LOG_INFO, "=======================");
LogConfigIndent(LogConfigGlobal());
Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
ParseeConfigLoad("parsee.json");
ParseeConfigInit();
parsee_conf = ParseeConfigGet();
#ifdef PLATFORM_IPHONE
Log(LOG_WARNING, "Wait. Are you running this on an iPhone?");
Log(LOG_WARNING, "You *ought* to have spoofed this, haven't you?");
Log(LOG_WARNING, "Simply jealous of you for doing this.");
#endif
LogConfigIndent(LogConfigGlobal());
{
ArgParseState state;
char *opts = ParseeGenerateGetopt(arguments);
int flag;
int xmpp = 8;
int http = 8;
ArgParseStateInit(&state);
while ((flag = ArgParse(&state, args, "gH:J:")) != -1)
while ((flag = ArgParse(&state, args, opts)) != -1)
{
switch (flag)
{
case 'h':
ParseeGenerateHelp(arguments);
Free(opts);
goto end;
case 'H':
http = strtol(state.optArg, NULL, 10);
break;
@ -70,46 +158,155 @@ Main(Array *args, HashMap *env)
/* Write out the config file to a YAML document */
Log(LOG_INFO, "Generating YAML...");
yaml = StreamOpen("parsee.yaml", "w");
ParseeConfigLoad(configuration);
ParseeConfigInit();
ParseeExportConfigYAML(yaml);
StreamClose(yaml);
Free(opts);
goto end;
case 'v':
switch (++verbose)
{
case PARSEE_VERBOSE_LOG:
LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG);
break;
case PARSEE_VERBOSE_TIMINGS:
Log(LOG_DEBUG, "Logging bench information.");
break;
case PARSEE_VERBOSE_STANZA:
Log(LOG_DEBUG, "Enabling stanza printing.");
break;
case PARSEE_VERBOSE_COMICAL:
Log(LOG_DEBUG, "What?");
Log(LOG_DEBUG, "No, but like, what do you except?");
Log(LOG_DEBUG, "Like do you want to log _every_ instruction?");
Log(LOG_DEBUG, "Like just every single thing %s does?", NAME);
Log(LOG_DEBUG, " ( why??? )");
Log(LOG_DEBUG, ".....................................");
Log(LOG_DEBUG, "Argh.");
Log(LOG_DEBUG, "Alright. I'll do my best.");
Log(LOG_DEBUG, "Get what you paid for.");
break;
}
break;
case 'C':
if (!UtilLastModified(state.optArg))
{
Log(LOG_ERR, "Invalid config: %s", state.optArg);
Log(LOG_ERR, "Ignoring.");
break;
}
configuration = state.optArg;
break;
case '?':
Log(LOG_ERR, "INVALID ARGUMENT GIVEN");
Free(opts);
goto end;
}
}
ParseeSetThreads(xmpp, http);
Free(opts);
}
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Loading configuration...");
}
ParseeConfigLoad(configuration);
ParseeConfigInit();
parsee_conf = ParseeConfigGet();
if (!parsee_conf)
{
goto end;
}
ParseeSetThreads(xmpp, http);
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
))
{
Log(LOG_ERR, "Could not connect to XMPP...");
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Destroying component...");
}
XMPPEndCompStream(jabber);
goto end;
}
Log(LOG_NOTICE, "Creating volatile tables...");
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising JID table");
}
ParseeInitialiseJIDTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising OID table");
}
ParseeInitialiseOIDTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising head table");
}
ParseeInitialiseHeadTable();
Log(LOG_NOTICE, "Setting up local Matrix user...");
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising nick table");
}
ParseeInitialiseNickTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising affiliation table");
}
ParseeInitialiseAffiliationTable();
conf.port = parsee_conf->port;
conf.threads = parsee_conf->http_threads;
conf.maxConnections = conf.threads << 2;
conf.handlerArgs = ParseeInitData(jabber);
conf.handler = ParseeRequest;
if (!conf.handlerArgs)
{
goto end;
}
Log(LOG_DEBUG, "Verbosity level: %d", verbose);
((ParseeData *) conf.handlerArgs)->verbosity = verbose;
Log(LOG_NOTICE, "Setting up local Matrix user...");
if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart))
{
char *parsee = ParseeMXID(conf.handlerArgs);
ASSetAvatar(parsee_conf,
parsee,
"mxc://tedomum.net/"
"7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136"
);
ASSetName(parsee_conf, parsee, "Parsee bridge");
Free(parsee);
}
Log(LOG_NOTICE, "Starting up local cronjobs...");
cron = CronCreate( 10 SECONDS );
CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs);
cron = CronCreate(10 SECONDS);
CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs);
CronEvery(cron, 10 SECONDS, ParseeCheckMatrix, conf.handlerArgs);
ParseeCleanup(conf.handlerArgs);
CronStart(cron);
@ -121,8 +318,9 @@ Main(Array *args, HashMap *env)
}
server = HttpServerCreate(&conf);
((ParseeData *) conf.handlerArgs)->server = server;
if (!ParseeInitialiseSignals(server, xmpp_thr, jabber))
if (!ParseeInitialiseSignals(conf.handlerArgs, xmpp_thr))
{
goto end;
}
@ -146,8 +344,12 @@ end:
CronStop(cron);
CronFree(cron);
ParseeFreeData(conf.handlerArgs);
ParseeDestroyAffiliationTable();
ParseeDestroyNickTable();
ParseeDestroyOIDTable();
ParseeDestroyHeadTable();
ParseeDestroyJIDTable();
(void) env;
return 0;
}

View file

@ -8,9 +8,56 @@
#include <stdlib.h>
#include <StanzaBuilder.h>
#include <Unistring.h>
#include <Matrix.h>
#include <AS.h>
static const char *
GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to);
static char *
JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char *hash)
{
char *sender = GrabString(event, 1, "sender");
Unistr *uninick = UnistrCreate(name);
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;
UnistrFree(uninick);
UnistrFree(filtered);
/* TODO: vCards! */
while (!XMPPJoinMUC(data->jabber, jid, rev, hash, -1, true) && nonce < 32)
{
char *nonce_str = StrInt(nonce);
char *input = StrConcat(3, sender, name, nonce_str);
char *hex = ParseeHMACS(data->id, input);
if (strlen(hex) >= 8)
{
hex[8] = '\0';
}
Free(nick);
Free(rev);
nick = StrConcat(4, name, "[", hex, "]");
rev = StrConcat(3, muc, "/", nick);
nonce++;
Free(nonce_str);
Free(input);
Free(hex);
}
ParseePushNickTable(muc, sender, nick);
Free(nick);
return (rev);
}
static void
ParseeMemberHandler(ParseeData *data, HashMap *event)
{
@ -51,20 +98,95 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key))
{
char *jid = ParseeEncodeMXID(state_key);
char *sha = NULL, *mime = NULL;
char *avatar = ASGetAvatar(data->config, NULL, state_key);
char *url = ParseeToUnauth(data, avatar, NULL);
chat_id = ParseeGetFromRoomID(data, room_id);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
Free(avatar);
avatar = NULL;
if (chat_id)
{
char *muc = ParseeGetMUCID(data, chat_id);
char *rev = StrConcat(2, muc, "/parsee");
char *name = ASGetName(data->config, room_id, state_key);
char *jabber = JoinMUC(data, event, jid, muc, name, sha);
avatar = ASGetAvatar(data->config, NULL, state_key);
Log(LOG_DEBUG, "MATRIX: Joining as '%s' (avatar=%s)", jabber, avatar);
XMPPJoinMUC(data->jabber, jid, rev);
Free(rev);
Free(jabber);
Free(avatar);
Free(name);
Free(muc);
avatar = NULL;
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */
}
Free(jid);
else
{
char *full_jid = StrConcat(3,
jid, "@", data->config->component_host
);
XMLElement *elem, *pevent, *items, *item, *meta, *info;
Log(LOG_DEBUG, "MATRIX: Got local user '%s'(mxid=%s)", jid, state_key);
elem = XMLCreateTag("message");
{
#define PUBSUB "http://jabber.org/protocol/pubsub"
#define AVATAR "urn:xmpp:avatar:metadata"
pevent = XMLCreateTag("event");
XMLAddAttr(pevent, "xmlns", PUBSUB "#event");
{
items = XMLCreateTag("items");
item = XMLCreateTag("item");
XMLAddAttr(items, "node", AVATAR);
XMLAddAttr(item, "id", sha);
{
meta = XMLCreateTag("metadata");
info = XMLCreateTag("info");
XMLAddAttr(meta, "xmlns", AVATAR);
XMLAddAttr(info, "id", sha);
XMLAddAttr(info, "url", url);
XMLAddAttr(info, "type", mime);
XMLAddChild(meta, info);
XMLAddChild(item, meta);
}
XMLAddChild(items, item);
XMLAddChild(pevent, items);
}
XMLAddChild(elem, pevent);
#undef PUBSUB
}
/* TODO: Broadcast PEP avatar change */
ParseeBroadcastStanza(data, full_jid, elem);
XMLFreeElement(elem);
elem = XMLCreateTag("presence");
{
XMLElement *x = XMLCreateTag("x");
XMLElement *photo;
XMLAddAttr(x, "xmlns", "vcard-temp:x:update");
photo = XMLCreateTag("photo");
XMLAddChild(photo, XMLCreateText(sha));
XMLAddChild(x, photo);
XMLAddChild(elem, x);
}
ParseeBroadcastStanza(data, full_jid, elem);
XMLFreeElement(elem);
Free(full_jid);
}
Free(chat_id);
Free(avatar);
Free(mime);
Free(sha);
Free(jid);
Free(url);
}
else if ((StrEquals(membership, "leave") ||
StrEquals(membership, "ban"))
@ -82,14 +204,29 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
/* If it can't be found, try to see if it's as a DM */
char *info_from = NULL, *info_to = NULL;
const char *type = GetXMPPInformation(data, event, &info_from, &info_to);
if (StrEquals(type, "chat"))
{
char *jid_to = ParseeTrimJID(info_to);
Log(LOG_DEBUG, "('%s'->'%s') is gone.", state_key, info_to);
/* TODO: Send a last DM, signifying that all is gone. */
ParseeDeleteDM(data, state_key, jid_to);
Free(jid_to);
}
Free(info_from);
Free(info_to);
goto end;
}
/* TODO: Check the name's validity */
name = ASGetName(data->config, room_id, state_key);
rev = StrConcat(4, muc_id, "/", name, "[p]");
name = StrDuplicate(ParseeLookupNick(muc_id, sender));
rev = StrConcat(3, muc_id, "/", name);
XMPPLeaveMUC(jabber, jid, rev, reason);
ParseePushNickTable(muc_id, sender, NULL);
end:
Free(chat_id);
Free(muc_id);
@ -118,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")
));
Free(profile);
return;
}
@ -135,7 +267,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("You are not authorised to do this.")
MatrixCreateNotice("You are not authorised to do this."), 0
));
Free(profile);
return;
@ -157,12 +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;
XMPPComponent *jabber = data ? data->jabber : NULL;
DbRef *room_data = NULL;
HashMap *data_json = NULL;
bool direct = false;
if (!data || !event || !from || !to)
@ -194,7 +324,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
else
{
char *matrix_name, *muc_join_as;
char *matrix_name = NULL, *matrix_avatar = NULL;
char *mime = NULL, *sha = NULL;
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
@ -208,18 +339,16 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
matrix_name = ASGetName(data->config, room_id, matrix_sender);
muc_join_as = StrConcat(4, muc_id, "/", matrix_name, "[p]");
/* TODO: Manage name conflicts. That would have been an easy
* task(try the original one, and use a counter if it fails),
* but that'd involve modifying the rest of the code, which
* I'm not doing at 01:39 ... */
XMPPJoinMUC(jabber, *from, muc_join_as);
matrix_avatar = ASGetAvatar(data->config, NULL, matrix_sender);
ASGetMIMESHA(data->config, matrix_avatar, &mime, &sha);
Free(JoinMUC(data, event, *from, muc_id, matrix_name, sha));
*to = muc_id;
Free(matrix_avatar);
Free(matrix_name);
Free(muc_join_as);
Free(mime);
Free(sha);
}
Free(chat_id);
@ -229,27 +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))
{
@ -261,9 +410,6 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
chat_id = ParseeGetFromRoomID(data, id);
/* TODO: This ref should be marked as read-only,
* as LMDB doesn't seem to like having two concurrent RW
* transactions running. */
ref = DbLockIntent(data->db, DB_HINT_READONLY, 3, "rooms", id, "data");
json = DbJson(ref);
direct = JsonValueAsBoolean(HashMapGet(json, "is_direct"));
@ -283,38 +429,44 @@ 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,
encoded_from, "@", jabber->host
);
if (direct)
{
xmppified_user = ParseeEncodeMXID(m_sender);
to = StrDuplicate(user);
Free(chat_id);
}
else
{
char *name, *rev;
char *name, *mime = NULL, *sha = NULL;
char *avatar;
/* Try to find the chat ID */
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
goto end;
}
xmppified_user = ParseeEncodeMXID(m_sender);
/* TODO: Check the name's validity.
* Is there a good way to check for that that isn't
* just "await on join and try again?" */
/* TODO: Avoid using the AS endpoints */
name = ASGetName(data->config, id, m_sender);
rev = StrConcat(4, muc_id, "/", name, "[p]");
avatar = ASGetAvatar(data->config, NULL, m_sender);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
XMPPJoinMUC(jabber, xmppified_user, rev);
Free(JoinMUC(data, event, encoded_from, muc_id, name, sha));
to = muc_id;
Free(sha);
Free(mime);
Free(name);
Free(rev);
Free(avatar);
}
if (reply_id)
{
/* TODO: Monocles chat DM users HATE this trick!
@ -342,13 +494,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *xmpp_ident = StrRandom(32);
builder = CreateStanzaBuilder(xmppified_user, to, xmpp_ident);
SetStanzaType(builder, type);
SetStanzaBody(builder, xepd ? xepd : body);
SetStanzaBody(builder, unauth ? unauth : (xepd ? xepd : body));
SetStanzaReply(builder, stanza, sender);
SetStanzaLink(builder, unauth);
SetStanzaEdit(builder, origin_id);
SetStanzaXParsee(builder, event);
WriteoutStanza(builder, jabber);
WriteoutStanza(builder, jabber, data->config->max_stanza_size);
DestroyStanzaBuilder(builder);
if (direct)
@ -371,6 +523,7 @@ end:
Free(stanza);
Free(sender);
Free(unauth);
Free(encoded_from);
Free(unedited_id);
DbUnlock(data->db, ref);
@ -402,8 +555,7 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
return;
}
else if (StrEquals(event_type, "m.room.message") ||
StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker
* support here... */
StrEquals(event_type, "m.sticker"))
{
ParseeMessageHandler(data, event);
Free(parsee);

68
src/MatrixID.c Normal file
View file

@ -0,0 +1,68 @@
#include <Matrix.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h>
UserID *
MatrixParseID(char *user)
{
UserID *ret = NULL;
char *localstart, *serverstart;
if (!user || *user != '@')
{
return NULL;
}
localstart = user + 1;
serverstart = strchr(user, ':');
if (!*localstart || !serverstart || localstart == serverstart)
{
return NULL;
}
if (!*++serverstart)
{
return NULL;
}
ret = Malloc(sizeof(*ret));
memset(ret, '\0', sizeof(*ret));
memcpy(ret->localpart, localstart, serverstart - localstart - 1);
memcpy(ret->server, serverstart, strlen(serverstart));
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

@ -11,210 +11,15 @@
static ParseeConfig *config = NULL;
static char *
GetLine(void)
{
Stream *input = StreamStdin();
char *out = NULL;
size_t length;
UtilGetLine(&out, &length, input);
if (out)
{
char *line = strchr(out, '\n');
if (line)
{
*line = '\0';
}
}
return out;
}
#include <stdarg.h>
static char *
PromptString(const char *expression, const char *def, ...)
{
Stream *output = StreamStdout();
char *out = NULL;
va_list ap;
while (!out)
{
va_start(ap, def);
StreamVprintf(output, expression, ap);
if (def)
{
StreamPrintf(output, " [%s]", def);
}
StreamPrintf(output, ": ");
StreamFlush(output);
va_end(ap);
out = GetLine();
if (!*out)
{
Free(out);
out = NULL;
if (def)
{
return StrDuplicate(def);
}
}
Log(LOG_INFO, "R=%s", out);
}
return out;
}
static int
PromptInteger(const char *expression, int def, ...)
{
Stream *output = StreamStdout();
char *out;
long l;
va_list ap;
va_start(ap, def);
StreamVprintf(output, expression, ap);
if (def >= 0)
{
StreamPrintf(output, " [%d]", def);
}
StreamPrintf(output, ": ");
StreamFlush(output);
va_end(ap);
while (true)
{
char *inval;
out = GetLine();
l = strtol(out, &inval, 10);
Free(out);
/* Not a use-after-free, as we reference only the addresses. */
if (l != 0 || inval != out)
{
break;
}
if (def >= 0)
{
return def;
}
}
return l;
}
/* TODO: Memleaks, galore! */
void
ParseeConfigInit(void)
{
Stream *stream;
HashMap *json;
if (config)
{
return;
}
/* TODO: Give the user an achievement at the end, just because they're
* cool. */
Log(LOG_NOTICE, "It seems like it is the first time you have configured ");
Log(LOG_NOTICE, "Parsee.");
Log(LOG_NOTICE, "As such, I need to ask you a couple of questions before ");
Log(LOG_NOTICE, "being able to use it.");
Log(LOG_NOTICE, "(don't worry; it won't take too long.)");
Log(LOG_NOTICE, "");
Log(LOG_NOTICE, "");
config = Malloc(sizeof(*config));
config->as_token = StrRandom(32);
config->hs_token = StrRandom(32);
config->http_threads = 8;
config->xmpp_threads = 8;
config->db_size = 64 MB;
/* TODO: This is NOT user friendly, and I know it! */
config->sender_localpart = PromptString(
"Name of the bridge bot, used for commands and bridged rooms",
"_parsee_bridge"
);
config->namespace_base = PromptString(
"Base namespace for Parsee (so foo@bar.com => @[NS]_foo=40bar.com)",
"_jabber"
);
config->listen_as = StrDuplicate("localhost");
config->port = PromptInteger(
"Matrix port for the AS service to use",
7642 /* proposed by Saint */
);
config->component_host = PromptString(
"XMPP component to be used for the configuration",
NULL
);
config->component_port = PromptInteger(
"XMPP port for to use for '%s'",
5347, config->component_host
);
config->shared_comp_secret = PromptString(
"%s's shared secret",
NULL, config->component_host
);
config->homeserver_host = PromptString(
"Delegated homeserver to be used for the configuration",
NULL
);
config->homeserver_port = PromptInteger(
"HTTP port for to use for '%s'",
443, config->homeserver_host
);
config->db_path = PromptString(
"Base directory for Parsee data",
NULL
);
config->media_base = PromptString(
"Base media URL for bridged media",
NULL
);
/* TODO: Make that configurable. */
config->server_base = StrDuplicate(config->homeserver_host);
Log(LOG_NOTICE, "Done! Please look over to the parsee.yaml file, ");
Log(LOG_NOTICE, "and follow the instructions listed in it. Then, ");
Log(LOG_NOTICE, "restart Parsee. ");
Log(LOG_NOTICE, "------------------------------------------------");
stream = StreamOpen("parsee.json", "w");
json = HashMapCreate();
HashMapSet(json, "as_token", JsonValueString(config->as_token));
HashMapSet(json, "hs_token", JsonValueString(config->hs_token));
HashMapSet(json, "sender", JsonValueString(config->sender_localpart));
HashMapSet(json, "namespace", JsonValueString(config->namespace_base));
HashMapSet(json, "listen_as", JsonValueString(config->listen_as));
HashMapSet(json, "port", JsonValueInteger(config->port));
HashMapSet(json, "hs_base", JsonValueString(config->server_base));
HashMapSet(json, "hs_host", JsonValueString(config->homeserver_host));
HashMapSet(json, "hs_port", JsonValueInteger(config->homeserver_port));
HashMapSet(json, "component_host", JsonValueString(config->component_host));
HashMapSet(json, "component_port", JsonValueInteger(config->component_port));
HashMapSet(json, "shared_comp_secret", JsonValueString(config->shared_comp_secret));
HashMapSet(json, "db", JsonValueString(config->db_path));
JsonEncode(json, stream, JSON_PRETTY);
JsonFree(json);
StreamClose(stream);
Log(LOG_ERR, "No config file found.");
Log(LOG_ERR, "Please use parsee-config to initialise %s.", NAME);
}
void
ParseeConfigLoad(char *conf)
@ -225,12 +30,18 @@ ParseeConfigLoad(char *conf)
{
return;
}
stream = StreamOpen("parsee.json", "r");
stream = StreamOpen(conf ? conf : "parsee.json", "r");
if (!stream)
{
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( \
@ -239,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;
@ -253,10 +67,25 @@ 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");
if (!config->max_stanza_size)
{
/* Standard XMPP "minimum" maximum */
config->max_stanza_size = 10000;
}
CopyToBool(ignore_bots, "ignore_bots");
CopyToStr(media_base, "media_base");
@ -272,6 +101,7 @@ ParseeSetThreads(int xmpp, int http)
{
if (!config)
{
Achievement("THREAD COUNT REQUEST WITHOUT CONFIG", true);
return;
}
config->http_threads = http;
@ -283,6 +113,7 @@ ParseeExportConfigYAML(Stream *stream)
{
if (!stream || !config)
{
Achievement("YAML EXPORT REQUEST WITHOUT CONFIG", true);
return;
}
StreamPrintf(stream, "# Autogenerated YAML AS entry for %s\n", NAME);
@ -296,6 +127,7 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token);
StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart);
StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n");
StreamPrintf(stream, "receive_ephemeral: true\n"); /* TODO: Actually use that field */
StreamPrintf(stream, "\n");
StreamPrintf(stream, "namespaces: \n");
StreamPrintf(stream, " users:\n");
@ -304,6 +136,7 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, " aliases:\n");
StreamPrintf(stream, " - exclusive: true\n");
StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base);
StreamFlush(stream);
}
void
@ -314,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);

View file

@ -6,17 +6,18 @@
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <Routes.h>
#include <Glob.h>
#include <AS.h>
ParseeData *
ParseeInitData(XMPPComponent *comp)
{
char *version;
ParseeData *data;
DbRef *ref;
if (!ParseeConfigGet())
{
return NULL;
@ -26,8 +27,15 @@ ParseeInitData(XMPPComponent *comp)
data->config = ParseeConfigGet();
data->router = HttpRouterCreate();
data->jabber = comp;
data->muc = CreateMUCServer(data);
data->handler = CommandCreateRouter();
data->oid_servers = HashMapCreate();
pthread_mutex_init(&data->oidl, NULL);
data->halted = false;
pthread_mutex_init(&data->halt_lock, NULL);
if (data->config->db_size)
{
data->db = DbOpenLMDB(data->config->db_path, data->config->db_size);
@ -35,10 +43,47 @@ ParseeInitData(XMPPComponent *comp)
if (!data->db)
{
Log(LOG_WARNING, "LMDB doesn't seem to be setup.");
Log(LOG_WARNING, "Falling back to flat-file.");
if (data->config->db_size)
{
Log(LOG_WARNING, "Falling back to flat-file.");
}
data->db = DbOpen(data->config->db_path, 0);
}
if (!(ref = DbLock(data->db, 1, "info")))
{
char *id = StrRandom(64);
ref = DbCreate(data->db, 1, "info");
HashMapSet(DbJson(ref), "identifier", JsonValueString(id));
HashMapSet(DbJson(ref), "version", JsonValueString(VERSION));
Free(id);
}
version = GrabString(DbJson(ref), 1, "version");
if (version && !ParseeIsCompatible(VERSION, version))
{
Log(LOG_WARNING, "Version mismatch(curr=%s db=%s).", VERSION, version);
Log(LOG_WARNING, "Yeah. You may want to _not_ do that.");
Log(LOG_WARNING, "(Parsee still needs an upgradepath mechanism.)");
DbUnlock(data->db, ref);
DbClose(data->db);
HashMapFree(data->oid_servers);
pthread_mutex_destroy(&data->oidl);
XMPPEndCompStream(data->jabber);
HttpRouterFree(data->router);
CommandFreeRouter(data->handler);
Free(data);
return NULL;
}
data->id = StrDuplicate(GrabString(DbJson(ref), 1, "identifier"));
DbUnlock(data->db, ref);
#define X_ROUTE(path, func) do {\
if (!HttpRouterAdd(data->router, path, func))\
{\
@ -56,12 +101,23 @@ ParseeInitData(XMPPComponent *comp)
void
ParseeFreeData(ParseeData *data)
{
char *entity;
XMLElement *disco;
if (!data)
{
return;
}
while (HashMapIterate(data->oid_servers, &entity, (void **) &disco))
{
XMLFreeElement(disco);
}
HashMapFree(data->oid_servers);
pthread_mutex_destroy(&data->oidl);
pthread_mutex_destroy(&data->halt_lock);
Free(data->id);
XMPPEndCompStream(data->jabber);
FreeMUCServer(data->muc);
DbClose(data->db);
HttpRouterFree(data->router);
CommandFreeRouter(data->handler);
@ -77,8 +133,6 @@ ParseeCleanup(void *datp)
size_t i;
uint64_t ts = UtilTsMillis();
Log(LOG_NOTICE, "Cleaning up...");
chats = DbList(data->db, 1, "chats");
for (i = 0; i < ArraySize(chats); i++)
@ -128,9 +182,12 @@ ParseeCleanup(void *datp)
} \
while (0)
CleanupField(stanza, 30 MINUTES, 50);
CleanupField(event, 30 MINUTES, 50);
CleanupField(id, 30 MINUTES, 50);
/* TODO: Custom retention period for any 1.0 */
CleanupField(stanza, 30 MINUTES, 500);
CleanupField(event, 30 MINUTES, 500);
CleanupField(id, 30 MINUTES, 500);
/* TODO: Also cleanup user cache information */
#undef CleanupField
}
DbListFree(chats);
@ -181,252 +238,15 @@ ParseeCleanup(void *datp)
} \
while (0)
CleanupField(stanza, 3 HOURS, 50);
CleanupField(event, 3 HOURS, 50);
CleanupField(id, 3 HOURS, 50);
CleanupField(stanza, 3 HOURS, 500);
CleanupField(event, 3 HOURS, 500);
CleanupField(id, 3 HOURS, 500);
DbUnlock(data->db, ref);
}
DbListFree(chats);
}
int
ParseeFindDatastart(char *data)
{
char *startline;
bool found = false;
if (!data)
{
return 0;
}
startline = data;
while (startline)
{
char *endline = strchr(startline, '\n');
if (*startline != '>')
{
found = true;
break;
}
startline = endline ? endline + 1 : NULL;
}
if (!found)
{
return 0;
}
return (int) (startline - data);
}
#include <StringStream.h>
typedef struct XMPPFlags {
bool quote;
} XMPPFlags;
static char *
XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
{
char *xepd = NULL, *tmp = NULL;
size_t i;
XMLElement *child;
char *reply_id = JsonValueAsString(
JsonGet(event, 4,
"content", "m.relates_to", "m.in_reply_to", "event_id"
));
char *room_id = JsonValueAsString(HashMapGet(event, "room_id"));
HashMap *referenced;
char *subxep;
#define Concat(strp) do \
{ \
size_t cidx; \
size_t len = strp ? strlen(strp) : 0; \
for (cidx = 0; cidx < len; cidx++) \
{ \
char cch[2] = { strp[cidx], 0 }; \
char nch = *cch ? strp[cidx+1] : '\0'; \
bool c = *cch == '\n' && nch != '>'; \
if (c && flags.quote) \
{ \
tmp = xepd; \
xepd = StrConcat(2, xepd, "\n>"); \
Free(tmp); \
continue; \
} \
tmp = xepd; \
xepd = StrConcat(2, xepd, cch); \
Free(tmp); \
} \
} \
while (0)
switch (elem->type)
{
case XML_ELEMENT_DATA:
Concat(elem->data);
break;
case XML_ELEMENT_TAG:
if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong"))
{
Concat("*");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("*");
}
else if (StrEquals(elem->name, "em"))
{
Concat("_");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("_");
}
else if (StrEquals(elem->name, "code"))
{
Concat("`");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("`");
}
else if (StrEquals(elem->name, "mx-reply"))
{
char *str;
referenced = ASFind(ParseeConfigGet(), room_id, reply_id);
str = JsonValueAsString(
JsonGet(referenced, 2, "content", "body")
);
if (!str)
{
JsonFree(referenced);
return xepd;
}
Concat(">");
flags.quote = true;
Concat(str);
flags.quote = false;
Concat("\n");
JsonFree(referenced);
}
else if (StrEquals(elem->name, "blockquote"))
{
Concat(">");
flags.quote = true;
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
flags.quote = false;
Concat("\n");
}
else if (StrEquals(elem->name, "br"))
{
Concat("\n");
/* HTML fucking SUCKS */
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("\n");
}
else if (StrEquals(elem->name, "a"))
{
char *href = HashMapGet(elem->attrs, "href");
Concat("(");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat(" points to ");
Concat(href);
Concat(" )");
}
else
{
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
}
break;
default:
break;
}
return xepd;
}
char *
ParseeXMPPify(HashMap *event)
{
char *type, *format, *html;
char *xepd = NULL;
XMLElement *elem;
XMPPFlags flags;
if (!event)
{
return NULL;
}
/* Check if it is a message event. */
type = JsonValueAsString(HashMapGet(event, "type"));
if (!StrEquals(type, "m.room.message"))
{
return NULL;
}
format = JsonValueAsString(JsonGet(event, 2, "content", "format"));
if (!StrEquals(format, "org.matrix.custom.html"))
{
/* Settle for the raw body instead. */
char *body = JsonValueAsString(JsonGet(event, 2, "content", "body"));
return StrDuplicate(body);
}
html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body"));
html = StrConcat(3, "<html>", html, "</html>");
elem = XMLCDecode(StrStreamReader(html), true, true);
flags.quote = false;
xepd = XMPPifyElement(event, elem, flags);
XMLFreeElement(elem);
Free(html);
return xepd;
}
void
ParseePushDMStanza(ParseeData *data, char *room_id, char *stanza_id, char *id, char *ev, char *sender)
{
@ -519,6 +339,15 @@ ParseePushStanza(ParseeData *data, char *chat_id, char *stanza_id, char *id, cha
}
/* TODO */
{
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (j)
{
JsonValueFree(HashMapSet(j, "ts", JsonValueInteger(age)));
}
DbUnlock(data->db, ref);
}
{
ref = DbCreate(data->db, 4, "chats", chat_id, "stanzas", stanza_id);
j = DbJson(ref);
@ -721,140 +550,178 @@ end:
return ret;
}
void
ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
void
ParseeUnlinkRoom(ParseeData *data, char *chat_id)
{
char *muc, *room;
DbRef *ref;
HashMap *j, *obj;
if (!data || !glob)
if (!data || !chat_id)
{
return;
}
ref = DbLock(data->db, 1, "global_bans");
if (!ref)
muc = ParseeGetMUCID(data, chat_id);
room = ParseeGetRoomID(data, chat_id);
if (!muc || !room)
{
ref = DbCreate(data->db, 1, "global_bans");
Free(muc);
Free(room);
return;
}
j = DbJson(ref);
obj = HashMapCreate();
if (reason)
{
HashMapSet(obj, "reason", JsonValueString(reason));
}
HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis()));
JsonValueFree(HashMapSet(j, glob, JsonValueObject(obj)));
ref = DbLock(data->db, 1, "chats");
JsonValueFree(HashMapDelete(
GrabObject(DbJson(ref), 1, "rooms"),
room
));
JsonValueFree(HashMapDelete(
GrabObject(DbJson(ref), 1, "mucs"),
muc
));
DbUnlock(data->db, ref);
DbDelete(data->db, 2, "chats", chat_id);
Free(muc);
Free(room);
}
bool
ParseeManageBan(ParseeData *data, char *user, char *room)
bool
ParseeIsMUCWhitelisted(ParseeData *data, char *muc)
{
char *server, *serv_start, *postserv;
DbRef *ref;
HashMap *j;
char *key;
JsonValue *val;
bool banned = false , matches = false;
if (!data || !user)
bool ret;
if (!data || !muc)
{
return false;
}
ref = DbLock(data->db, 1, "global_bans");
j = DbJson(ref);
while (HashMapIterate(j, &key, (void **) &val))
if (!DbExists(data->db, 1, "whitelist"))
{
HashMap *obj = JsonValueAsObject(val);
if (matches)
{
continue;
}
if (GlobMatches(key, user))
{
banned = true;
matches = true;
if (room)
{
/* TODO: Use the object to set the reason */
ASBan(data->config, room, user);
(void) obj;
}
}
return true;
}
serv_start = strchr(muc, '@');
serv_start = serv_start ? serv_start : muc;
server = StrDuplicate(serv_start + 1);
postserv = server ? strchr(server, '/') : NULL;
if (postserv) /* GCC doesn't know strchr is pure. */
{
*postserv = '\0';
}
DbUnlock(data->db, ref);
return banned;
}
char *
ParseeStringifyDate(uint64_t millis)
{
uint64_t rest = millis;
uint64_t hours, minutes, seconds;
char *hs, *ms, *ss, *out;
hours = rest / (1 HOURS);
rest = rest % (1 HOURS);
minutes = rest / (1 MINUTES);
rest = rest % (1 MINUTES);
seconds = rest / (1 SECONDS);
hs = StrInt(hours);
ms = StrInt(minutes);
ss = StrInt(seconds);
out = StrConcat(8,
hours ? hs : "",
hours ? " hours" : "",
(hours && minutes) ? ", " : "",
minutes ? ms : "",
minutes ? " minutes" : "",
(minutes && seconds) ? ", " : "",
seconds ? ss : "",
seconds ? " seconds" : ""
ref = DbLockIntent(data->db,
DB_HINT_READONLY,
1, "whitelist"
);
Free(hs);
Free(ms);
Free(ss);
ret = HashMapGet(DbJson(ref), server);
DbUnlock(data->db, ref);
Free(server);
return out;
return ret;
}
void
ParseeAchievement(const char *func, const char *msg, bool die)
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat)
{
Log(LOG_ERR, "=========== Achievement GET! ===========");
Log(LOG_ERR, "%s: %s.", func, msg);
Log(LOG_ERR, "THIS IS, LET'S SAY, NOT GOOD, AND A SIGN OF ");
Log(LOG_ERR, "A PROGRAMMER ERROR. PLEASE KILLALL -9 PARSEE.");
Log(LOG_ERR, "");
Log(LOG_ERR, "YOU, HOWEVER, GET TO WIN AN AWARD FOR THIS.");
Log(LOG_ERR, "I AM SIMPLY JEALOUS OF YOU GETTING SUCH A ");
Log(LOG_ERR, "GOOD ERROR MESSAGE.");
Log(LOG_ERR, "=========== Achievement GET! ===========");
HashMap *ret, *json;
DbRef *ref;
if (die)
{
abort();
}
}
char *
ParseeGenerateMTO(char *common_id)
{
char *matrix_to;
if (!common_id)
char *key;
JsonValue *value;
if (!data || !chat)
{
return NULL;
}
common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", common_id);
Free(common_id);
ref = DbLockIntent(data->db, DB_HINT_READONLY,
3, "chats", chat, "settings"
);
json = DbJson(ref);
if (!ref)
{
return HashMapCreate();
}
return matrix_to;
ret = HashMapCreate();
while (HashMapIterate(json, &key, (void **) &value))
{
char *str = JsonValueAsString(value);
HashMapSet(ret, key, StrDuplicate(str));
}
DbUnlock(data->db, ref);
return ret;
}
void
ParseeFreeChatSettings(HashMap *settings)
{
char *key;
void *val;
if (!settings)
{
return;
}
while (HashMapIterate(settings, &key, &val))
{
Free(val);
}
HashMapFree(settings);
}
char *
ParseeGetChatSetting(ParseeData *data, char *chat, char *key)
{
HashMap *map;
char *ret;
if (!data || !chat || !key)
{
return NULL;
}
map = ParseeGetChatSettings(data, chat);
ret = StrDuplicate(HashMapGet(map, key));
ParseeFreeChatSettings(map);
return ret;
}
void
ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val)
{
DbRef *ref;
HashMap *json;
if (!data || !chat || !key || !val)
{
return;
}
ref = DbLockIntent(data->db, DB_HINT_WRITE,
3, "chats", chat, "settings"
);
if (!ref)
{
ref = DbCreate(data->db, 3, "chats", chat, "settings");
}
json = DbJson(ref);
JsonValueFree(HashMapSet(json, key, JsonValueString(val)));
DbUnlock(data->db, ref);
return;
}
bool
ParseeIsMediaEnabled(ParseeData *data, char *chat_id)
{
char *value;
bool ret;
if (!data || !chat_id)
{
return false;
}
ret = !StrEquals(
(value = ParseeGetChatSetting(data, chat_id, "p.media.enabled")),
"false"
);
Free(value);
return ret;
}

74
src/Parsee/HMAC.c Normal file
View file

@ -0,0 +1,74 @@
/* CC0 implementation of a HMAC system based on Cytoplasm.
* Ignore the scary "Parsee.h", its practically unused. */
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Str.h>
#include <string.h>
static void
ComputeKPad(char *key, uint8_t pad, uint8_t *kopad)
{
size_t klen;
uint8_t *kp;
uint8_t kpi[64] = { 0 };
size_t i;
if ((klen = strlen(key)) <= 64)
{
kp = Malloc(klen * sizeof(uint8_t));
memcpy(kp, key, klen);
}
else
{
kp = (uint8_t *) Sha256(key);
klen = 32;
}
memset(kpi, 0x00, 64);
memcpy(kpi, kp, klen);
Free(kp);
/* Now that we have K', lets compute it XORd with opad */
for (i = 0; i < 64; i++)
{
uint8_t byte = kpi[i];
kopad[i] = byte ^ pad;
}
}
char *
ParseeHMAC(char *key, uint8_t *msg, size_t msglen)
{
uint8_t opad[64], ipad[64 + msglen];
uint8_t outer[64 + 32];
uint8_t *innersha;
uint8_t *sha;
char *str;
if (!key || !msg || !msglen)
{
return NULL;
}
/* Initialise K' XOR opad and K' XOR ipad */
ComputeKPad(key, 0x5C, opad);
ComputeKPad(key, 0x36, ipad);
/* Compute H((K' XOR ipad) || msg) */
memcpy(ipad + 64, msg, msglen);
innersha = Sha256Raw(ipad, 64 + msglen);
/* Compute (K' XOR opad) || H((K' XOR ipad) || msg) */
memcpy(outer, opad, 64);
memcpy(outer + 64, innersha, 32);
/* Compute H((K' XOR opad) || H((K' XOR ipad) || msg)) */
sha = Sha256Raw(outer, 64 + 32);
str = ShaToHex(sha, HASH_SHA256);
Free(innersha);
Free(sha);
return str;
}

View file

@ -1,194 +0,0 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t lock;
static HashMap *jid_table = NULL;
void
ParseeInitialiseJIDTable(void)
{
if (jid_table)
{
return;
}
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
jid_table = HashMapCreate();
pthread_mutex_unlock(&lock);
}
void
ParseePushJIDTable(char *muc, char *bare)
{
if (!muc || !bare || !jid_table)
{
return;
}
pthread_mutex_lock(&lock);
bare = ParseeTrimJID(bare);
Free(HashMapSet(jid_table, muc, bare));
pthread_mutex_unlock(&lock);
}
char *
ParseeLookupJID(char *muc)
{
char *bare;
if (!muc || !jid_table)
{
return NULL;
}
pthread_mutex_lock(&lock);
bare = StrDuplicate(HashMapGet(jid_table, muc));
pthread_mutex_unlock(&lock);
if (!bare)
{
bare = StrDuplicate(muc);
}
return bare;
}
void
ParseeDestroyJIDTable(void)
{
char *key;
void *val;
if (!jid_table)
{
return;
}
pthread_mutex_lock(&lock);
while (HashMapIterate(jid_table, &key, &val))
{
Free(val);
}
HashMapFree(jid_table);
jid_table = NULL;
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
}
static pthread_mutex_t head_lock;
static HashMap *head_table = NULL;
void
ParseeInitialiseHeadTable(void)
{
if (head_table)
{
return;
}
pthread_mutex_init(&head_lock, NULL);
pthread_mutex_lock(&head_lock);
head_table = HashMapCreate();
pthread_mutex_unlock(&head_lock);
}
void
ParseePushHeadTable(char *room, char *event)
{
if (!room || !event || !head_table)
{
return;
}
pthread_mutex_lock(&head_lock);
event = StrDuplicate(event);
Free(HashMapSet(head_table, room, event));
pthread_mutex_unlock(&head_lock);
}
char *
ParseeLookupHead(char *room)
{
char *event;
if (!room || !head_table)
{
return NULL;
}
pthread_mutex_lock(&head_lock);
event = StrDuplicate(HashMapGet(head_table, room));
pthread_mutex_unlock(&head_lock);
return event;
}
void
ParseeDestroyHeadTable(void)
{
char *key;
void *val;
if (!head_table)
{
return;
}
pthread_mutex_lock(&head_lock);
while (HashMapIterate(head_table, &key, &val))
{
Free(val);
}
HashMapFree(head_table);
head_table = NULL;
pthread_mutex_unlock(&head_lock);
pthread_mutex_destroy(&head_lock);
}
static pthread_mutex_t oid_lock;
static HashMap *oid_table = NULL;
void
ParseeInitialiseOIDTable(void)
{
if (oid_table)
{
return;
}
pthread_mutex_init(&oid_lock, NULL);
pthread_mutex_lock(&oid_lock);
oid_table = HashMapCreate();
pthread_mutex_unlock(&oid_lock);
}
void
ParseePushOIDTable(char *muc, char *bare)
{
if (!muc || !bare || !oid_table)
{
return;
}
pthread_mutex_lock(&oid_lock);
bare = StrDuplicate(bare);
Free(HashMapSet(oid_table, muc, bare));
pthread_mutex_unlock(&oid_lock);
}
char *
ParseeLookupOID(char *muc)
{
char *bare;
if (!muc || !oid_table)
{
return NULL;
}
pthread_mutex_lock(&oid_lock);
bare = StrDuplicate(HashMapGet(oid_table, muc));
pthread_mutex_unlock(&oid_lock);
return bare;
}
void
ParseeDestroyOIDTable(void)
{
char *key;
void *val;
if (!oid_table)
{
return;
}
pthread_mutex_lock(&oid_lock);
while (HashMapIterate(oid_table, &key, &val))
{
Free(val);
}
HashMapFree(oid_table);
oid_table = NULL;
pthread_mutex_unlock(&oid_lock);
pthread_mutex_destroy(&oid_lock);
}

25
src/Parsee/Logo.c Normal file
View file

@ -0,0 +1,25 @@
#include <Parsee.h>
#include <Cytoplasm/Log.h>
const char *parsee_ascii[PARSEE_ASCII_LINES] =
{
" =+======",
" || | _ _/__----",
" / || \\ ==+= _/_____\\_",
" | || | -|- L___J ",
"_/ || \\_ ||| .______\\",
" || | | | |.____.|",
" || / | \\ |L____||",
" _// | | J"
};
void
ParseePrintASCII(void)
{
size_t i;
for (i = 0; i < PARSEE_ASCII_LINES; i++)
{
Log(LOG_INFO, "%s", parsee_ascii[i]);
}
}

57
src/Parsee/SHA.c Normal file
View file

@ -0,0 +1,57 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Sha.h>
static char *
ToHex(unsigned char *input, size_t length)
{
char *hex = Malloc(length * 2 + 1);
size_t i;
for (i = 0; i < length; i++)
{
const char *table =
"0123456789abcdef";
unsigned char byte = input[i];
hex[2 * i] = table[(byte >> 4) & 0xF];
hex[2*i+1] = table[(byte >> 0) & 0xF];
}
hex[length * 2] = '\0';
return hex;
}
char *
ParseeSHA256(char *string)
{
unsigned char *sha;
char *returnString;
if (!string)
{
return NULL;
}
sha = Sha256(string);
returnString = ToHex(sha, 32);
Free(sha);
return returnString;
}
char *
ParseeSHA1(char *string)
{
unsigned char *sha;
char *returnString;
if (!string)
{
return NULL;
}
sha = Sha1(string);
returnString = ToHex(sha, 20);
Free(sha);
return returnString;
}

View file

@ -0,0 +1,106 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t affi_lock;
static HashMap *affi_table = NULL;
typedef struct XMPPStatus {
char *role;
char *affiliation;
} XMPPStatus;
static XMPPStatus *
CreateStatus(char *role, char *affiliation)
{
XMPPStatus *ret;
if (!role || !affiliation)
{
return NULL;
}
ret = Malloc(sizeof(*ret));
ret->role = StrDuplicate(role);
ret->affiliation = StrDuplicate(affiliation);
return ret;
}
static void
FreeStatus(XMPPStatus *status)
{
if (!status)
{
return;
}
Free(status->affiliation);
Free(status->role);
Free(status);
}
void
ParseeInitialiseAffiliationTable(void)
{
if (affi_table)
{
return;
}
pthread_mutex_init(&affi_lock, NULL);
pthread_mutex_lock(&affi_lock);
affi_table = HashMapCreate();
pthread_mutex_unlock(&affi_lock);
}
void
ParseePushAffiliationTable(char *user, char *affi, char *role)
{
XMPPStatus *status;
if (!user || !affi || !role)
{
return;
}
pthread_mutex_lock(&affi_lock);
status = CreateStatus(role, affi);
FreeStatus(HashMapSet(affi_table, user, status));
pthread_mutex_unlock(&affi_lock);
}
bool
ParseeLookupAffiliation(char *user, char **affiliation, char **role)
{
XMPPStatus *status;
if (!user || !affiliation || !role)
{
return false;
}
pthread_mutex_lock(&affi_lock);
status = HashMapGet(affi_table, user);
*affiliation = StrDuplicate(status ? status->affiliation : NULL);
*role = StrDuplicate(status ? status->role : NULL);
pthread_mutex_unlock(&affi_lock);
return !!status;
}
void
ParseeDestroyAffiliationTable(void)
{
char *key;
void *val;
if (!affi_table)
{
return;
}
pthread_mutex_lock(&affi_lock);
while (HashMapIterate(affi_table, &key, &val))
{
FreeStatus(val);
}
HashMapFree(affi_table);
affi_table = NULL;
pthread_mutex_unlock(&affi_lock);
pthread_mutex_destroy(&affi_lock);
}

View file

@ -0,0 +1,67 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t head_lock;
static HashMap *head_table = NULL;
void
ParseeInitialiseHeadTable(void)
{
if (head_table)
{
return;
}
pthread_mutex_init(&head_lock, NULL);
pthread_mutex_lock(&head_lock);
head_table = HashMapCreate();
pthread_mutex_unlock(&head_lock);
}
void
ParseePushHeadTable(char *room, char *event)
{
if (!room || !event || !head_table)
{
return;
}
pthread_mutex_lock(&head_lock);
event = StrDuplicate(event);
Free(HashMapSet(head_table, room, event));
pthread_mutex_unlock(&head_lock);
}
char *
ParseeLookupHead(char *room)
{
char *event;
if (!room || !head_table)
{
return NULL;
}
pthread_mutex_lock(&head_lock);
event = StrDuplicate(HashMapGet(head_table, room));
pthread_mutex_unlock(&head_lock);
return event;
}
void
ParseeDestroyHeadTable(void)
{
char *key;
void *val;
if (!head_table)
{
return;
}
pthread_mutex_lock(&head_lock);
while (HashMapIterate(head_table, &key, &val))
{
Free(val);
}
HashMapFree(head_table);
head_table = NULL;
pthread_mutex_unlock(&head_lock);
pthread_mutex_destroy(&head_lock);
}

View file

@ -0,0 +1,72 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t lock;
static HashMap *jid_table = NULL;
void
ParseeInitialiseJIDTable(void)
{
if (jid_table)
{
return;
}
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
jid_table = HashMapCreate();
pthread_mutex_unlock(&lock);
}
void
ParseePushJIDTable(char *muc, char *bare)
{
if (!muc || !bare || !jid_table)
{
return;
}
pthread_mutex_lock(&lock);
bare = ParseeTrimJID(bare);
Free(HashMapSet(jid_table, muc, bare));
pthread_mutex_unlock(&lock);
}
char *
ParseeLookupJID(char *muc)
{
char *bare;
if (!muc || !jid_table)
{
return NULL;
}
pthread_mutex_lock(&lock);
bare = StrDuplicate(HashMapGet(jid_table, muc));
pthread_mutex_unlock(&lock);
if (!bare)
{
bare = StrDuplicate(muc);
}
return bare;
}
void
ParseeDestroyJIDTable(void)
{
char *key;
void *val;
if (!jid_table)
{
return;
}
pthread_mutex_lock(&lock);
while (HashMapIterate(jid_table, &key, &val))
{
Free(val);
}
HashMapFree(jid_table);
jid_table = NULL;
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
}

View file

@ -0,0 +1,100 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t nick_lock;
static HashMap *nick_table = NULL;
static char *
GenerateKey(char *muc, char *mxid)
{
char *concatStr;
char *hexDigest;
if (!muc || !mxid)
{
return NULL;
}
concatStr = StrConcat(3, muc, ":", mxid);
hexDigest = ParseeSHA256(concatStr);
Free (concatStr);
return hexDigest;
}
void
ParseeInitialiseNickTable(void)
{
if (nick_table)
{
return;
}
pthread_mutex_init(&nick_lock, NULL);
pthread_mutex_lock(&nick_lock);
nick_table = HashMapCreate();
pthread_mutex_unlock(&nick_lock);
}
void
ParseePushNickTable(char *muc, char *mxid, char *nick)
{
char *key;
if (!muc || !mxid || !nick_table)
{
return;
}
pthread_mutex_lock(&nick_lock);
key = GenerateKey(muc, mxid);
nick = StrDuplicate(nick);
if (nick)
{
Free(HashMapSet(nick_table, key, nick));
}
else
{
Free(HashMapDelete(nick_table, key));
}
Free(key);
pthread_mutex_unlock(&nick_lock);
}
char *
ParseeLookupNick(char *muc, char *mxid)
{
char *ret, *key;
if (!muc || !nick_table)
{
return NULL;
}
pthread_mutex_lock(&nick_lock);
key = GenerateKey(muc, mxid);
ret = HashMapGet(nick_table, key);
Free(key);
pthread_mutex_unlock(&nick_lock);
return ret;
}
void
ParseeDestroyNickTable(void)
{
char *key;
void *val;
if (!nick_table)
{
return;
}
pthread_mutex_lock(&nick_lock);
while (HashMapIterate(nick_table, &key, &val))
{
Free(val);
}
HashMapFree(nick_table);
nick_table = NULL;
pthread_mutex_unlock(&nick_lock);
pthread_mutex_destroy(&nick_lock);
}

View file

@ -0,0 +1,68 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t oid_lock;
static HashMap *oid_table = NULL;
void
ParseeInitialiseOIDTable(void)
{
if (oid_table)
{
return;
}
pthread_mutex_init(&oid_lock, NULL);
pthread_mutex_lock(&oid_lock);
oid_table = HashMapCreate();
pthread_mutex_unlock(&oid_lock);
}
void
ParseePushOIDTable(char *muc, char *bare)
{
if (!muc || !bare || !oid_table)
{
return;
}
pthread_mutex_lock(&oid_lock);
bare = StrDuplicate(bare);
Free(HashMapSet(oid_table, muc, bare));
pthread_mutex_unlock(&oid_lock);
}
char *
ParseeLookupOID(char *muc)
{
char *bare;
if (!muc || !oid_table)
{
return NULL;
}
pthread_mutex_lock(&oid_lock);
bare = StrDuplicate(HashMapGet(oid_table, muc));
pthread_mutex_unlock(&oid_lock);
return bare;
}
void
ParseeDestroyOIDTable(void)
{
char *key;
void *val;
if (!oid_table)
{
return;
}
pthread_mutex_lock(&oid_lock);
while (HashMapIterate(oid_table, &key, &val))
{
Free(val);
}
HashMapFree(oid_table);
oid_table = NULL;
pthread_mutex_unlock(&oid_lock);
pthread_mutex_destroy(&oid_lock);
}

View file

@ -1,42 +1,46 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Log.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <Matrix.h>
bool
ParseeIsPuppet(const ParseeConfig *conf, char *user)
{
char *localpart;
UserID *id;
bool flag = true;
size_t len;
if (!user || !conf || *user != '@')
if (!user ||
!conf ||
*user != '@' ||
!(id = MatrixParseID(user)))
{
return false;
}
localpart = user + 1;
len = strlen(conf->namespace_base);
if (strncmp(localpart, conf->namespace_base, len))
if (strncmp(id->localpart, conf->namespace_base, len))
{
flag = false;
}
len = strlen(conf->sender_localpart);
if (!strncmp(localpart, conf->sender_localpart, len))
if (StrEquals(id->localpart, conf->sender_localpart))
{
flag = true;
}
/* TODO: Check serverpart. 2 Parsee instances may be running in the same
* room. */
flag = flag && StrEquals(id->server, conf->server_base);
Free(id);
return flag;
}
char *
@ -93,7 +97,7 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid)
data_start = jid_flags;
while (*data_start && *data_start != '_')
{
/* TODO: Make this a macro */
/* TODO: Get rid of this */
if (*data_start == 'l')
{
plain_jid = true;
@ -123,7 +127,7 @@ ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias)
data_start = jid_flags;
while (*data_start && *data_start != '_')
{
/* TODO: Make this a macro */
/* TODO: Get rid of this */
if (*data_start == 'm')
{
plain_jid = true;
@ -144,14 +148,15 @@ char *
ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
{
char *ret, *tmp;
size_t i;
size_t i, len;
if (!c || !jid)
{
return NULL;
}
ret = StrConcat(2, c->namespace_base, "_l_");
for (i = 0; i < strlen(jid); i++)
len = strlen(jid);
for (i = 0; i < len; i++)
{
char cpy = jid[i];
char cs[4] = { 0 };
@ -161,7 +166,7 @@ ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
/* RID: Break everything and die. */
break;
}
if (islower(*cs) || isalnum(*cs) || *cs == '_' ||
if (islower((int) *cs) || isalnum((int) *cs) || *cs == '_' ||
*cs == '=' || *cs == '-' || *cs == '/' ||
*cs == '+' || *cs == '.')
{
@ -190,7 +195,7 @@ char *
ParseeGetLocal(char *mxid)
{
char *cpy;
size_t i;
size_t i, len;
if (!mxid)
{
return NULL;
@ -200,12 +205,14 @@ ParseeGetLocal(char *mxid)
return StrDuplicate(mxid);
}
mxid++;
cpy = Malloc(strlen(mxid) + 1);
memset(cpy, '\0', strlen(mxid) + 1);
memcpy(cpy, mxid, strlen(mxid));
len = strlen(mxid);
for (i = 0; i < strlen(mxid); i++)
mxid++;
cpy = Malloc(len + 1);
memset(cpy, '\0', len + 1);
memcpy(cpy, mxid, len);
for (i = 0; i < len; i++)
{
if (cpy[i] == ':')
{
@ -221,19 +228,19 @@ char *
ParseeEncodeMXID(char *mxid)
{
char *ret;
size_t i, j;
size_t i, j, len;
if (!mxid)
{
return NULL;
}
/* Worst case scenario of 3-bytes the char */
ret = Malloc(strlen(mxid) * 3 + 1);
for (i = 0, j = 0; i < strlen(mxid); i++)
len = strlen(mxid);
ret = Malloc(len * 3 + 1);
for (i = 0, j = 0; i < len; i++)
{
char src = mxid[i];
/* TODO: More robust system */
if (src <= 0x20 ||
(src == '"' || src == '&' ||
src == '\'' || src == '/' ||
@ -295,19 +302,17 @@ char *
ParseeGetDMID(char *mxid, char *jid)
{
char *concat, *sha;
unsigned char *raw;
if (!mxid || !jid)
{
return NULL;
}
/* We really don't care about safety here. */
jid = ParseeTrimJID(jid);
concat = StrConcat(2, mxid, jid);
raw = Sha1(concat);
sha = ShaToHex(raw);
sha = ParseeSHA1(concat);
Free(concat);
Free(raw);
Free(jid);
return sha;
@ -353,19 +358,34 @@ ParseePushDMRoom(ParseeData *d, char *mxid, char *jid, char *r)
Free(dmid);
return;
}
void
ParseeDeleteDM(ParseeData *d, char *mxid, char *jid)
{
char *dmid;
if (!d || !mxid || !jid)
{
return;
}
dmid = ParseeGetDMID(mxid, jid);
DbDelete(d->db, 2, "users", dmid);
Free(dmid);
return;
}
char *
ParseeTrimJID(char *jid)
{
char *ret;
size_t i;
size_t i, len;
if (!jid)
{
return NULL;
}
ret = StrDuplicate(jid);
for (i = 0; i < strlen(ret); i++)
len = strlen(ret);
for (i = 0; i < len; i++)
{
if (ret[i] == '/')
{
@ -519,7 +539,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id)
return ret;
}
void
ParseeSendPresence(ParseeData *data)
{
@ -538,10 +557,16 @@ ParseeSendPresence(ParseeData *data)
while (HashMapIterate(mucs, &muc, (void **) &val))
{
char *rev = StrConcat(2, muc, "/parsee");
char *chat_id = ParseeGetFromMUCID(data, muc);
DbRef *chat = DbLockIntent(data->db, DB_HINT_READONLY, 2, "chats", chat_id);
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", rev);
XMPPJoinMUC(data->jabber, "parsee", rev);
XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false);
DbUnlock(data->db, chat);
Free(chat_id);
Free(rev);
}
DbUnlock(data->db, ref);
@ -663,11 +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;
#define PAT "%s/_matrix/client/v1/media/download/%s%s"
char *key, *hmac;
#define PAT "%s/media/%s%s?hmac=%s"
#define PATF "%s/media/%s%s/%s?hmac=%s"
size_t l;
if (!data || !mxc)
{
@ -684,19 +711,51 @@ ParseeToUnauth(ParseeData *data, char *mxc)
return NULL;
}
/* TODO: HTTPS */
l = snprintf(NULL, 0,
PAT,
data->config->media_base,
url->host, url->path
);
key = StrConcat(2, url->host, url->path);
hmac = ParseeHMACS(data->id, key);
Free(key);
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
);
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

@ -0,0 +1,26 @@
#include <Parsee.h>
#include <Cytoplasm/Log.h>
#include <stdlib.h>
void
ParseeAchievement(const char *func, const char *msg, bool die)
{
Log(LOG_ERR, "=========== Achievement GET! ===========");
Log(LOG_ERR, "%s: %s.", func, msg);
Log(LOG_ERR, "THIS IS, LET'S SAY, NOT GOOD, AND A SIGN OF ");
Log(LOG_ERR, "A PROGRAMMER ERROR. PLEASE KILLALL -9 PARSEE.");
Log(LOG_ERR, "");
Log(LOG_ERR, "YOU, HOWEVER, GET TO WIN AN AWARD FOR THIS.");
Log(LOG_ERR, "I AM SIMPLY JEALOUS OF YOU GETTING SUCH A ");
Log(LOG_ERR, "GOOD ERROR MESSAGE.");
Log(LOG_ERR, "=========== Achievement GET! ===========");
if (die)
{
abort();
}
}

View file

@ -0,0 +1,53 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
void
ParseeGenerateHelp(const Argument *list)
{
if (!list)
{
return;
}
while (!list->end)
{
char *str = list->value_req ?
StrConcat(3, " [", list->value_descr, "]") :
StrDuplicate("");
Log(LOG_INFO, "-%c%s", list->argument, str);
LogConfigIndent(LogConfigGlobal());
Log(LOG_INFO, "%s", list->description);
LogConfigUnindent(LogConfigGlobal());
list++;
Free(str);
}
return;
}
char *
ParseeGenerateGetopt(const Argument *list)
{
char *ret = NULL, *tmp = NULL;
if (!list)
{
return NULL;
}
while (!list->end)
{
char buffer[3] = {
list->argument, list->value_req ? ':' : '\0',
'\0'
};
tmp = ret;
ret = StrConcat(2, ret, buffer);
Free(tmp);
list++;
}
return ret;
}

View file

@ -0,0 +1,323 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#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>
typedef struct XMPPFlags {
bool quote;
} XMPPFlags;
static char *
XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags)
{
char *xepd = NULL, *tmp = NULL;
size_t i;
XMLElement *child;
char *reply_id = GrabString(
event,
4, "content",
"m.relates_to", "m.in_reply_to", "event_id"
);
char *room_id = JsonValueAsString(HashMapGet(event, "room_id"));
HashMap *referenced;
char *subxep;
#define Concat(strp) do \
{ \
size_t cidx; \
size_t len = strp ? strlen(strp) : 0; \
for (cidx = 0; cidx < len; cidx++) \
{ \
char cch[2]; \
cch[0] = strp[cidx]; \
cch[1] = '\0'; \
char nch = *cch ? strp[cidx+1] : '\0'; \
bool c = *cch == '\n' && nch != '>'; \
if (c && flags.quote) \
{ \
tmp = xepd; \
xepd = StrConcat(2, xepd, "\n>"); \
Free(tmp); \
continue; \
} \
tmp = xepd; \
xepd = StrConcat(2, xepd, cch); \
Free(tmp); \
} \
} \
while (0)
switch (elem ? elem->type : -1)
{
case XML_ELEMENT_DATA:
Concat(elem->data);
break;
case XML_ELEMENT_TAG:
if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong"))
{
Concat("*");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("*");
}
else if (StrEquals(elem->name, "em"))
{
Concat("_");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("_");
}
else if (StrEquals(elem->name, "code"))
{
Concat("`");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("`");
}
else if (StrEquals(elem->name, "mx-reply"))
{
char *str;
referenced = ASFind(ParseeConfigGet(), room_id, reply_id);
str = JsonValueAsString(
JsonGet(referenced, 2, "content", "body")
);
if (!str)
{
JsonFree(referenced);
return xepd;
}
Concat(">");
flags.quote = true;
Concat(str);
flags.quote = false;
Concat("\n");
JsonFree(referenced);
}
else if (StrEquals(elem->name, "blockquote"))
{
Concat(">");
flags.quote = true;
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
flags.quote = false;
Concat("\n");
}
else if (StrEquals(elem->name, "br"))
{
Concat("\n");
/* HTML fucking SUCKS */
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
if (i != 0)
{
Concat("\n");
}
}
else if (StrEquals(elem->name, "a"))
{
char *href = HashMapGet(elem->attrs, "href");
Uri *pref = UriParse(href);
if (pref && StrEquals(pref->host, "matrix.to"))
{
/* TODO: Check if the element here is a Matrix.TO
* pointing to a Parsee user. */
UserID *id = MatrixParseIDFromMTO(pref);
if (id)
{
char *real_id = StrConcat(4, "@", id->localpart, ":", id->server);
/* TODO: Detect if it already is a Parsee user */
if (ParseeIsPuppet(conf, real_id))
{
char *name = ASGetName(conf, NULL, real_id);
Concat((name ? name : real_id));
Free(name);
}
else
{
Concat(real_id);
}
Free(real_id);
}
else
{
Concat(href);
}
Free(id);
}
else
{
for (i = 0; i < ArraySize(elem->children); i++)
{
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(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
}
break;
default:
break;
}
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(ParseeData *data, HashMap *event)
{
char *type, *format, *html;
char *xepd = NULL;
XMLElement *elem;
XMPPFlags flags;
if (!event)
{
return NULL;
}
/* Check if it is a message event. */
type = JsonValueAsString(HashMapGet(event, "type"));
if (!StrEquals(type, "m.room.message"))
{
return NULL;
}
if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html"))
{
/* Settle for the raw body instead. */
char *body = GetRawBody(event);
return StrDuplicate(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(data ? data->config : NULL, event, elem, flags);
XMLFreeElement(elem);
Free(html);
return xepd;
}
char *
ParseeGenerateMTO(char *common_id)
{
char *matrix_to;
if (!common_id)
{
return NULL;
}
/* TODO: Is HttpUrlEncode okay? */
common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", common_id);
Free(common_id);
return matrix_to;
}

97
src/Parsee/Utils/Nofly.c Normal file
View file

@ -0,0 +1,97 @@
#include <Parsee.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#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)
{
DbRef *ref;
HashMap *j, *obj;
if (!data || !glob)
{
return;
}
ref = DbLock(data->db, 1, "global_bans");
if (!ref)
{
ref = DbCreate(data->db, 1, "global_bans");
}
j = DbJson(ref);
obj = HashMapCreate();
if (reason)
{
HashMapSet(obj, "reason", JsonValueString(reason));
}
HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis()));
JsonValueFree(HashMapSet(j, glob, JsonValueObject(obj)));
DbUnlock(data->db, ref);
}
bool
ParseeManageBan(ParseeData *data, char *user, char *room)
{
DbRef *ref;
HashMap *j;
char *key;
JsonValue *val;
bool banned = false , matches = false;
if (!data || !user)
{
return false;
}
ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "global_bans");
j = DbJson(ref);
while (HashMapIterate(j, &key, (void **) &val))
{
HashMap *obj = JsonValueAsObject(val);
if (matches)
{
continue;
}
if (GlobMatches(key, user))
{
banned = true;
matches = true;
if (room)
{
/* TODO: Use the object to set the reason */
ASBan(data->config, room, user);
(void) obj;
}
}
}
DbUnlock(data->db, ref);
return banned;
}

95
src/Parsee/Utils/String.c Normal file
View file

@ -0,0 +1,95 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Unistring.h>
#include <stdbool.h>
#include <string.h>
int
ParseeFindDatastart(char *data)
{
char *startline;
bool found = false;
if (!data)
{
return 0;
}
startline = data;
while (startline)
{
char *endline = strchr(startline, '\n');
if (*startline != '>')
{
found = true;
break;
}
startline = endline ? endline + 1 : NULL;
}
if (!found)
{
return 0;
}
return (int) (startline - data);
}
int
ParseeFindDatastartU(char *data)
{
Unistr *str;
size_t ret;
if (!data)
{
return 0;
}
str = UnistrCreate(data);
ret = UnistrGetOffset(str, (uint32_t) '>');
UnistrFree(str);
return (int) ret;
}
char *
ParseeStringifyDate(uint64_t millis)
{
uint64_t rest = millis;
uint64_t hours, minutes, seconds;
char *hs, *ms, *ss, *out;
hours = rest / (1 HOURS);
rest = rest % (1 HOURS);
minutes = rest / (1 MINUTES);
rest = rest % (1 MINUTES);
seconds = rest / (1 SECONDS);
hs = StrInt(hours);
ms = StrInt(minutes);
ss = StrInt(seconds);
out = StrConcat(8,
hours ? hs : "",
hours ? " hours" : "",
(hours && minutes) ? ", " : "",
minutes ? ms : "",
minutes ? " minutes" : "",
(minutes && seconds) ? ", " : "",
seconds ? ss : "",
seconds ? " seconds" : ""
);
Free(hs);
Free(ms);
Free(ss);
return out;
}

53
src/Parsee/Versions.c Normal file
View file

@ -0,0 +1,53 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
bool
ParseeIsCompatible(char *ver1, char *ver2)
{
char *major1 = NULL;
char *major2 = NULL;
char *tmp;
if (!ver1 || !ver2)
{
return false;
}
/* Check if one of them is a "flipped"(joke) version. If so,
* then the user should definitely NOT try funny things with
* their data. */
if (*ver1 == '-' || *ver2 == '-')
{
return false;
}
#define GetMajor(v) do \
{ \
while (*ver##v != '.') \
{ \
char cb[2]; \
cb[0] = *ver##v; \
cb[1] = '\0'; \
tmp = major##v; \
major##v = StrConcat(2, major##v, cb); \
Free(tmp); \
ver##v++; \
} \
} \
while (0)
GetMajor(1);
GetMajor(2);
if (!StrEquals(major1, major2))
{
Free(major1);
Free(major2);
return false;
}
Free(major1);
Free(major2);
return true;
}

View file

@ -2,37 +2,80 @@
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Matrix.h>
#include <Parsee.h>
#include <AS.h>
#include <string.h>
static HttpClientContext *
TryDownload(ParseeData *data, char *server, char *identi)
{
HttpClientContext *cctx;
char *path;
server = HttpUrlEncode(server);
identi = HttpUrlEncode(identi);
path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi);
cctx = ParseeCreateRequest(data->config, HTTP_GET, path);
ASAuthenticateRequest(data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
if (HttpRequestSend(cctx) != HTTP_OK)
{
Log(LOG_WARNING, "Failing back.");
HttpClientContextFree(cctx);
path = StrConcat(4, "/_matrix/client/v1/media/download/", server, "/", identi);
cctx = ParseeCreateRequest(data->config, HTTP_GET, path);
ASAuthenticateRequest(data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
}
Free(server);
Free(identi);
return cctx;
}
RouteHead(RouteMedia, arr, argp)
{
ParseeHttpArg *args = argp;
HttpClientContext *cctx;
HashMap *reqh;
HashMap *reqh, *params;
char *server = ArrayGet(arr, 0);
char *identi = ArrayGet(arr, 1);
char *path, *key, *val;
char *key, *val;
char *hmac, *chkmak = NULL;
/* TODO: Make it check the DB for its validicity. "Purging" would be useful.
*/
if (!server || !identi)
params = HttpRequestParams(args->ctx);
hmac = HashMapGet(params, "hmac");
/* TODO: Make it check the DB for its validicity. "Purging" would be
* useful, alongside checking if someone isn't just a little idiotic. */
{
HttpResponseStatus(args->ctx, HTTP_BAD_REQUEST);
return MatrixCreateError("M_NOT_YET_UPLOADED", "No server/identifier");
char *concat = StrConcat(3, server, "/", identi);
chkmak = ParseeHMACS(args->data->id, concat);
Free(concat);
}
if (!server || !identi || !hmac || !StrEquals(hmac, chkmak))
{
char *err =
hmac && StrEquals(hmac, chkmak) ?
"No server/identifier/HMAC code" :
"Hah! You _dirty_ little liar! Try a little harder!";
Free(chkmak);
HttpResponseStatus(args->ctx, HTTP_BAD_REQUEST);
return MatrixCreateError("M_NOT_YET_UPLOADED", err);
}
Free(chkmak);
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);
/* Proxy the media through an authenticated endpoint if the HMAC
* is valid. */
cctx = TryDownload(args->data, server, identi);
reqh = HttpResponseHeaders(cctx);
while (HashMapIterate(reqh, &key, (void **) &val))
{
@ -45,8 +88,6 @@ RouteHead(RouteMedia, arr, argp)
}
HttpClientContextFree(cctx);
Free(server);
Free(identi);
return NULL;
}

View file

@ -25,13 +25,12 @@ RouteHead(RoutePing, arr, argp)
);
goto end;
}
Log(LOG_INFO, "Pong!");
RequestJSON();
/* TODO: Load ping info */
response = HashMapCreate();
end:
(void) arr;
JsonFree(request);
return response;
}

View file

@ -41,13 +41,26 @@ GetRandomQuote(void)
"We truly live in a " CODE "...",
"You truly lack the Desire Drive for this!",
"Eeh? Bifrost mode? Only bad servers use Bifrost mode!",
"'Wow parsee can save the world' - said a wise guy",
"As small as a dwarf, and can run on your pie!",
"It's all wicked unsafe, memory slow 🚀🚀🚀🚀",
"XMPP kinda sucks.",
"Matrix kinda sucks.",
"Throw jabs!"
"Throw jabs!",
"'Won't you hold my &lt;presence/&gt; safe?' - Kanako",
NAME ": the federated world's little little kobashi",
"Go take a look at your stanzas!",
"Go take a look at your objects!",
"DEC Alpha AXP-Certified!",
"this is the moment parsee started parsing or smth idk"
" - another wise person",
"Ah, merde, mon TGV est en retard de 53 minutes !"
};
const size_t count = sizeof(quotes)/sizeof(*quotes);
@ -69,6 +82,7 @@ RouteHead(RouteRoot, arr, argp)
{
P("<title>%s Lander</title>", NAME);
P("<meta charset='UTF-8'/>");
P("<link rel='icon' href='data:image/png;base64,%s'/>", media_parsee_logo);
P("<style>");
{
P("html {");
@ -76,6 +90,17 @@ RouteHead(RouteRoot, arr, argp)
P("color: #eee;");
P("font-family: sans-serif;");
P("}");
P("#ascii {");
P("text-align: center;");
P("color: #be1337;");
P("}");
P("#ascii pre {");
P("display: inline-block;");
P("text-align: left;");
P("}");
P("img {");
P("image-rendering: pixelated;");
P("}");
P("blockquote {");
P("border-left: 2px solid #ccc;");
P("margin: 1.5em 10px;");
@ -98,15 +123,25 @@ RouteHead(RouteRoot, arr, argp)
P("<body>");
{
P("<center><h1>");
P("Your %s is running, all with that %s!", NAME, CODE);
P("</h1></center>");
size_t i;
P("<center>");
P("<h1>Your %s is running, all with that %s!</h1>", NAME, CODE);
P("</center>");
P("<hr/>");
P("<blockquote><i>");
{
P("%s", GetRandomQuote());
}
P("</i></blockquote>");
P("<pre id='ascii'><code>");
for (i = 0; i < PARSEE_ASCII_LINES; i++)
{
XMLElement *e = XMLCreateText((char *) parsee_ascii[i]);
XMLEncode(args->stream, e);
XMLFreeElement(e);
P("<br/>");
}
P("</code></pre>");
P("<p>");
{
@ -144,7 +179,7 @@ RouteHead(RouteRoot, arr, argp)
P("<p>");
{
P("More information available at ");
P("<a");
P("<a ");
P("href='https://kappach.at/parsee'");
P(">the actual page</a>.");
}
@ -199,7 +234,7 @@ RouteHead(RouteRoot, arr, argp)
P("Some clicky links relating to %s:", NAME);
P("<ul>");
{
const char *fedi = "https://ak.ari.lt/parsee";
const char *fedi = "https://tengu.kappach.at/parsee";
const char *icon = "https://kappach.at/parsee.gif";
P("<li><a href='%s'>Repository</a></li>", REPOSITORY);
P("<li><a href='%s'>Fediverse</a></li>", fedi);
@ -219,5 +254,6 @@ RouteHead(RouteRoot, arr, argp)
P("</html>");
#undef P
(void) arr;
return NULL;
}

View file

@ -30,7 +30,6 @@ RouteHead(RouteTxns, arr, argp)
RequestJSON();
/* TODO: Do a thing with these. */
events = JsonValueAsArray(HashMapGet(request, "events"));
for (i = 0; i < ArraySize(events); i++)
{
@ -43,6 +42,7 @@ RouteHead(RouteTxns, arr, argp)
response = HashMapCreate();
end:
(void) arr;
JsonFree(request);
return response;
}

View file

@ -67,7 +67,8 @@ RouteHead(RouteRoomAck, arr, argp)
}
muc = ParseeDecodeLocalMUC(args->data->config, room);
if (ParseeManageBan(args->data, muc, NULL))
if (ParseeManageBan(args->data, muc, NULL) ||
ParseeIsMUCWhitelisted(args->data, muc))
{
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError(
@ -130,7 +131,7 @@ RouteHead(RouteRoomAck, arr, argp)
{
char *rev = StrConcat(2, muc, "/parsee");
Log(LOG_NOTICE, "Sending presence to %s", rev);
XMPPJoinMUC(args->data->jabber, "parsee", rev);
XMPPJoinMUC(args->data->jabber, "parsee", rev, NULL, -1, false);
Free(rev);
}

View file

@ -6,32 +6,35 @@
#include <XMPP.h>
static HttpServer *server = NULL;
static ParseeData *data;
static pthread_t xmpp_thr;
static XMPPComponent *jabber = NULL;
static void
SignalHandler(int signal)
{
if (server && (signal == SIGTERM || signal == SIGINT))
if (data->server && (signal == SIGTERM || signal == SIGINT))
{
Log(LOG_INFO, "Killing thread...");
XMPPFinishCompStream(jabber);
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(server);
HttpServerStop(data->server);
return;
}
}
bool
ParseeInitialiseSignals(HttpServer *s, pthread_t xmpp, XMPPComponent *j)
ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp)
{
struct sigaction sa;
server = s;
data = d;
xmpp_thr = xmpp;
jabber = j;
sigfillset(&sa.sa_mask);
sa.sa_handler = SignalHandler;

View file

@ -40,6 +40,7 @@ CreateStanzaBuilder(char *from, char *to, char *id)
builder->replying_to_stanza = NULL;
builder->replying_to_sender = NULL;
builder->editing = NULL;
builder->type = NULL;
builder->body = NULL;
builder->oob = NULL;
@ -184,14 +185,14 @@ ExportStanza(StanzaBuilder *builder)
builder->replying_to_sender &&
builder->body)
{
int off = ParseeFindDatastart(builder->body);
int off = ParseeFindDatastartU(builder->body);
char *ostr = StrInt(off);
XMLElement *reply = XMLCreateTag("reply");
XMLElement *fallback = XMLCreateTag("fallback");
XMLElement *fall_body = XMLCreateTag("body");
XMLAddAttr(reply, "to", builder->replying_to_stanza);
XMLAddAttr(reply, "id", builder->replying_to_sender);
XMLAddAttr(reply, "to", builder->replying_to_sender);
XMLAddAttr(reply, "id", builder->replying_to_stanza);
XMLAddAttr(reply, "xmlns", "urn:xmpp:reply:0");
XMLAddAttr(fallback, "xmlns", "urn:xmpp:fallback:0");
@ -227,22 +228,21 @@ ExportStanza(StanzaBuilder *builder)
}
void
WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber)
WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber, size_t max)
{
XMLElement *elem;
if (!builder || !jabber)
{
return;
}
if (!max)
{
max = 10000; /* XMPP recommended limit */
}
elem = ExportStanza(builder);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, elem);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, elem, max);
XMLFreeElement(elem);
return;
}
StanzaBuilder *
@ -258,7 +258,6 @@ SetStanzaXParsee(StanzaBuilder *builder, HashMap *e)
{
XMLElement *parsee_version, *ver_elem;
XMLElement *parsee_link, *link_elem;
XMLElement *parsee_text, *text_elem;
XMLElement *parsee_event, *event_elem;
XMLElement *parsee_json, *json_elem;
char *event_id = GrabString(e, 1, "event_id");
@ -285,16 +284,6 @@ SetStanzaXParsee(StanzaBuilder *builder, HashMap *e)
XMLAddChild(parsee_link, link_elem);
XMLAddChild(parsee, parsee_link);
parsee_text = XMLCreateTag("zayds-note");
text_elem = XMLCreateText("\"LDA HANG YOURSELF\" - Zayd");
XMLAddChild(parsee_text, text_elem);
XMLAddChild(parsee, parsee_text);
parsee_text = XMLCreateTag("mcnebs-note");
text_elem = XMLCreateText("LDA will never beat the allegations");
XMLAddChild(parsee_text, text_elem);
XMLAddChild(parsee, parsee_text);
if (event_id)
{
parsee_event = XMLCreateTag("event-id");

View file

@ -7,7 +7,6 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
char **
StrSplitLines(char *text)
@ -117,7 +116,7 @@ StrFullRect(char **split)
char
StrGet(StringRect *rect, int line, int col)
{
int actual_line, actual_col;
size_t actual_line, actual_col;
char *linep;
if (!rect || !rect->source_lines)
{
@ -150,7 +149,7 @@ StrGet(StringRect *rect, int line, int col)
size_t
StrViewChars(StringRect rect, int line)
{
int actual_line;
size_t actual_line;
char *linep;
if (!rect.source_lines)
{
@ -174,7 +173,7 @@ StrViewChars(StringRect rect, int line)
StringRect
StrGetl(StringRect *rect, int line, bool extend)
{
int actual_line;
size_t actual_line;
StringRect ret;
if (!rect->source_lines)
{
@ -204,7 +203,7 @@ StrGetl(StringRect *rect, int line, bool extend)
StringRect
StrShift(StringRect rect, int n)
{
int new = rect.start_char + n;
size_t new = rect.start_char + n;
if (new > rect.end_char)
{
new = rect.end_char;

View file

@ -7,125 +7,13 @@
#include <string.h>
typedef struct ReaderCookie {
size_t length;
size_t offset;
char *buffer;
} ReaderCookie;
#define Remaining() (cook->length - cook->offset)
static ssize_t
ReadStreamReader(void *coop, void *to, size_t n)
{
ReaderCookie *cook = coop;
size_t remaining;
if (!cook)
{
return 0;
}
remaining = Remaining();
if (n > remaining)
{
memcpy(to, cook->buffer + cook->offset, remaining);
cook->offset = cook->length;
return remaining;
}
memcpy(to, cook->buffer + cook->offset, n);
cook->offset += n;
return n;
}
static ssize_t
WriteStreamReader(void *coop, void *from, size_t n)
{
/* Writing to a stream reader is silly. */
return 0;
}
static off_t
SeekStreamReader(void *coop, off_t mag, int sgn)
{
ReaderCookie *cook = coop;
if (!cook)
{
return 0;
}
switch (sgn)
{
case SEEK_SET:
if (mag > cook->length)
{
cook->offset = cook->length;
return 0;
}
else if (mag < 0)
{
cook->offset = 0;
return 0;
}
cook->offset = mag;
return 0;
case SEEK_CUR:
cook->offset += mag;
if (cook->offset > cook->length)
{
cook->offset = cook->length;
}
else if (cook->offset < 0)
{
cook->offset = 0;
}
return 0;
case SEEK_END:
cook->offset += cook->length + mag;
if (cook->offset > cook->length)
{
cook->offset = cook->length;
}
else if (cook->offset < 0)
{
cook->offset = 0;
}
return 0;
}
return 0;
}
static int
CloseStreamReader(void *coop)
{
/* Nothing to free as of now. */
if (coop)
{
Free(coop);
}
return 0;
}
const static IoFunctions Functions = {
.read = ReadStreamReader,
.seek = SeekStreamReader,
.write = WriteStreamReader,
.close = CloseStreamReader,
};
Stream *
StrStreamReaderN(char *buffer, int n)
{
Io *raw_io;
ReaderCookie *cookie;
if (!buffer)
if (!buffer || n < 0)
{
return NULL;
}
cookie = Malloc(sizeof(*cookie));
cookie->buffer = buffer;
cookie->length = n ? n : strlen(buffer);
cookie->offset = 0;
raw_io = IoCreate(cookie, Functions);
return StreamIo(raw_io);
return StreamFile(fmemopen(buffer, n ? (size_t) n : strlen(buffer), "rb"));
}

View file

@ -11,6 +11,9 @@ static ssize_t
ReadStreamWriter(void *coop, void *to, size_t n)
{
/* Reading from a stream writer is silly. */
(void) coop;
(void) to;
(void) n;
return 0;
}
static ssize_t
@ -33,6 +36,9 @@ static off_t
SeekStreamWriter(void *coop, off_t mag, int sgn)
{
/* TODO: Seeking would be useful, though not supported yet. */
(void) coop;
(void) mag;
(void) sgn;
return 0;
}
@ -40,10 +46,11 @@ static int
CloseStreamWriter(void *coop)
{
/* Nothing to free as of now. */
(void) coop;
return 0;
}
const static IoFunctions Functions = {
static const IoFunctions Functions = {
.read = ReadStreamWriter,
.seek = SeekStreamWriter,
.write = WriteStreamWriter,

314
src/Unistr.c Normal file
View file

@ -0,0 +1,314 @@
#include <Unistring.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Log.h>
#include <string.h>
#include <stdarg.h>
struct Unistr {
size_t length;
uint32_t *codepoints;
};
void
UnistrAddch(Unistr *unistr, uint32_t u)
{
if (!unistr || !u)
{
return;
}
unistr->length++;
unistr->codepoints = Realloc(
unistr->codepoints,
unistr->length * sizeof(*unistr->codepoints)
);
unistr->codepoints[unistr->length - 1] = u;
}
static bool
UTFIsN(char *off, size_t available, size_t n, uint8_t pc)
{
size_t i;
uint8_t *offu = (uint8_t *) off;
if (((available < n) || ((*offu >> (8-n-1)) != pc)) && (n >= 1))
{
return false;
}
for (i = 0; i < n - 1; i++)
{
if ((offu[i+1] >> 6) != 0x2)
{
return false;
}
}
return true;
}
Unistr *
UnistrCreate(char *src)
{
size_t len, i;
Unistr *str;
if (!src)
{
return NULL;
}
len = strlen(src);
str = Malloc(sizeof(*str));
str->length = 0;
str->codepoints = NULL;
/* We can't just set the length to {len}. */
for (i = 0; i < len; i++)
{
char byte = src[i];
size_t available = len - i;
if ((byte & 0x80) == 0)
{
/* This is a regular codepoint */
UnistrAddch(str, byte & 0x7F);
continue;
}
else if (UTFIsN(&src[i], available, 2, 0x06))
{
char a = src[i+0] & 0x1F;
char b = src[i+1] & 0x3F;
uint32_t u = (a << (6 * 1)) | b;
/* Overlongs are errors. */
if (u < 0x0080 || u > 0x07FF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 2 - 1;
continue;
}
else if (UTFIsN(&src[i], available, 3, 0x0E))
{
char a = src[i+0] & 0x0F;
char b = src[i+1] & 0x3F;
char c = src[i+2] & 0x3F;
uint32_t u =
(a << (6 * 2)) |
(b << (6 * 1)) |
(c << (6 * 0)) ;
/* Overlongs are errors. */
if (u < 0x0800 || u > 0xFFFF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 3 - 1;
continue;
}
else if (UTFIsN(&src[i], available, 4, 0x1E))
{
char a = src[i+0] & 0x07;
char b = src[i+1] & 0x3F;
char c = src[i+2] & 0x3F;
char d = src[i+3] & 0x3F;
uint32_t u =
(a << (6 * 3)) |
(b << (6 * 2)) |
(c << (6 * 1)) |
(d << (6 * 0)) ;
/* Overlongs are errors. */
if (u < 0x10000 || u > 0x10FFFF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 4 - 1;
continue;
}
}
return str;
}
void
UnistrFree(Unistr *unistr)
{
if (!unistr)
{
return;
}
Free(unistr->codepoints);
Free(unistr);
}
char *
UnistrC(Unistr *unistr)
{
char *ret, *tmp, *utf;
size_t i;
if (!unistr)
{
return NULL;
}
ret = NULL;
for (i = 0; i < unistr->length; i++)
{
uint32_t code = unistr->codepoints[i];
utf = StrUtf8Encode(code);
tmp = ret;
ret = StrConcat(2, ret, utf);
Free(tmp);
Free(utf);
}
return ret;
}
size_t
UnistrSize(Unistr *unistr)
{
return unistr ? unistr->length : 0;
}
uint32_t
UnistrGetch(Unistr *unistr, size_t i)
{
if (!unistr)
{
return 0;
}
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)
{
return NULL;
}
return u <= 0xFFFF;
}
Unistr *
UnistrFilter(Unistr *str, UnistrFilterFunc filter)
{
Unistr *unistr;
size_t i;
if (!str || !filter)
{
return NULL;
}
unistr = UnistrCreate("");
for (i = 0; i < UnistrSize(str); i++)
{
uint32_t code = UnistrGetch(str, i);
if (!filter(code))
{
continue;
}
UnistrAddch(unistr, code);
}
return unistr;
}
Unistr *
UnistrConcat(size_t n, ...)
{
va_list list;
size_t i;
Unistr *ret = UnistrCreate("");
va_start(list, n);
for (i = 0; i < n; i++)
{
Unistr *to_concat = va_arg(list, Unistr *);
size_t j;
for (j = 0; j < UnistrSize(to_concat); j++)
{
UnistrAddch(ret, UnistrGetch(to_concat, j));
}
}
va_end(list);
return ret;
}
size_t
UnistrGetOffset(Unistr *str, uint32_t sep)
{
size_t i;
uint32_t prev = 0x0A;
if (!str || !sep)
{
return 0;
}
for (i = 0; i < str->length; i++)
{
uint32_t curr = str->codepoints[i];
if (prev == 0x0A && curr != sep)
{
return i;
}
prev = curr;
}
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

@ -80,7 +80,14 @@ DecodeQuote(StringRect rect, size_t *skip)
ret = rect;
ret.end_line--;
while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace(ch))
/* TODO: You can easily craft strings that conceal data(
* > Please mind the whitespace
* > hidden we're concealing data to Matrix
* > star in Well, you can still stare at the body and XML
* > four but that's for Nerds
* > seasons See, Touhou reference!
* concealing!) */
while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace((int) ch))
{
shift_by++;
}
@ -125,7 +132,7 @@ DecodeSpan(StringRect rect, char del, size_t *skip)
{
return StrFullRect(NULL);
}
if (!ret.source_lines && isspace(c))
if (!ret.source_lines && isspace((int) c))
{
return StrFullRect(NULL);
}
@ -322,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");
@ -365,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("~"));
@ -392,21 +399,37 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
char *
XEP393ToXMLString(XEP393Element *xepd)
{
XMLElement *root;
XMLElement *root, *act_root;
XMLElement *child;
Stream *writer;
char *ret = NULL;
size_t i, children;
if (!xepd)
{
return NULL;
}
root = XMLCreateTag("span");
act_root = root;
ShoveXML(xepd, root);
writer = StrStreamWriter(&ret);
XMLEncode(writer, root);
XMLFreeElement(root);
children = ArraySize(root->children);
child = ArrayGet(root->children, 0);
if (children == 1 && StrEquals(child->name, "p"))
{
children = ArraySize(child->children);
root = child;
}
for (i = 0; i < children; i++)
{
child = ArrayGet(root->children, i);
XMLEncode(writer, child);
}
XMLFreeElement(act_root);
StreamFlush(writer);
StreamClose(writer);

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);
@ -114,9 +120,19 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
void
XMLEncodeString(Stream *stream, char *data)
{
size_t i;
size_t i, len;
for (i = 0; i < strlen(data); i++)
if (!stream || !data)
{
return;
}
/* TODO: I should write a "Parsee Best Practice" guideline and make sure
* people understand to NOT constantly recompute lengths parameter on
* these kinds of loops. ArraySize is fine(since its indirection), but
* operations like strlen take time! */
len = strlen(data);
for (i = 0; i < len; i++)
{
char c = data[i];
if (c == '<')
@ -145,6 +161,9 @@ XMLEncodeString(Stream *stream, char *data)
continue;
}
StreamPrintf(stream, "%c", c);
/* TODO: Maybe consider Unistrings and encode arbitrary Unicode
* codepoints * with special XML. Oughta make it printable, you know?
*/
}
}
void

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. */
@ -295,7 +308,7 @@ static bool
XMLookahead(XMLexer *lexer, const char *str, bool skip)
{
int *stack;
size_t top, i;
size_t top, i, len;
ssize_t ntop;
bool ret = false;
if (!lexer || !str)
@ -304,9 +317,10 @@ XMLookahead(XMLexer *lexer, const char *str, bool skip)
}
top = 0;
stack = Malloc(strlen(str) * sizeof(*stack));
len = strlen(str);
stack = Malloc(len * sizeof(*stack));
for (i = 0; i < strlen(str); i++)
for (i = 0; i < len; i++)
{
char c = str[i];
int getc = XMLGetc(lexer);
@ -336,8 +350,8 @@ seekback:
return ret;
}
#define IsNamestart(c) ((c == ':') || isalpha(c) || (c == '_'))
#define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit(c))
#define IsNamestart(c) ((c == ':') || isalpha((int) c) || (c == '_'))
#define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit((int) c))
static char *
XMLParseName(XMLexer *lexer)
{
@ -582,6 +596,8 @@ XMLCreateEnd(XMLexer *lexer, char *end)
event->col = 0;
event->offset = 0;
(void) lexer;
return event;
}
static XMLEvent *
@ -690,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

@ -11,13 +11,15 @@
#include <errno.h>
#include <netdb.h>
#include <StringStream.h>
#include <Parsee.h>
#include <XML.h>
/* The default component port Prosody uses. */
#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;
@ -26,12 +28,26 @@ 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);
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, error_str
);
return NULL;
}
for (res = res0; res; res = res->ai_next)
{
@ -45,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;
@ -55,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);
@ -62,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;
}
@ -76,19 +104,15 @@ XMPPInitialiseCompStream(char *host, int port)
return comp;
}
#include <Cytoplasm/Sha.h>
static char *
ComputeHandshake(char *shared, char *stream)
{
char *source;
unsigned char *raw_sha;
char *sha;
source = StrConcat(2, stream, shared);
raw_sha = Sha1(source);
sha = ShaToHex(raw_sha);
sha = ParseeSHA1(source);
Free(raw_sha);
Free(source);
return sha;
@ -96,7 +120,6 @@ ComputeHandshake(char *shared, char *stream)
bool
XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
{
/* TODO */
XMLexer *sax;
XMLEvent *ev;
char *stream_id, *handshake;
@ -129,7 +152,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
}
break;
}
if (ev->type != XML_LEXER_STARTELEM ||
if (!ev || ev->type != XML_LEXER_STARTELEM ||
!StrEquals(ev->element, "stream:stream"))
{
Log(LOG_ERR, "Excepted stream:stream element.");
@ -140,7 +163,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
stream_id = StrDuplicate(HashMapGet(ev->attrs, "id"));
handshake = ComputeHandshake(shared, stream_id);
Log(LOG_NOTICE, "- sID='%s'", stream_id);
Log(LOG_DEBUG, "- sID='%s'", stream_id);
StreamPrintf(stream, "<handshake>%s</handshake>", handshake);
StreamFlush(stream);
XMLFreeEvent(ev);
@ -154,11 +177,54 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
}
break;
}
if (ev->type != XML_LEXER_ELEM ||
if (!ev || ev->type != XML_LEXER_ELEM ||
!StrEquals(ev->element, "handshake"))
{
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);
@ -169,7 +235,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
ret = true;
/* We can uhh, send stanzas, and receive them! */
Log(LOG_INFO, "Communications to '%s' established.", as);
Log(LOG_DEBUG, "Communications to '%s' established.", as);
XMLFreeEvent(ev);
Free(stream_id);
@ -199,8 +265,43 @@ XMPPEndCompStream(XMPPComponent *comp)
{
return;
}
pthread_mutex_destroy(&comp->write_lock);
pthread_mutex_lock(&comp->write_lock);
StreamClose(comp->stream);
comp->stream = NULL;
pthread_mutex_unlock(&comp->write_lock);
pthread_mutex_destroy(&comp->write_lock);
Free(comp->host);
Free(comp);
}
void
XMPPSendStanza(XMPPComponent *comp, XMLElement *stanza, size_t max)
{
size_t len;
char *c = NULL;
Stream *stringWriter;
if (!comp || !stanza)
{
return;
}
stringWriter = StrStreamWriter(&c);
XMLEncode(stringWriter, stanza);
StreamFlush(stringWriter);
StreamClose(stringWriter);
if (c && max && (len = strlen(c)) > max)
{
Log(LOG_WARNING,
"Unexceptedly large stanza received (len=%d max=%d).",
(int) len, (int) max
);
Free(c);
return;
}
pthread_mutex_lock(&comp->write_lock);
StreamPrintf(comp->stream, "%s", c);
StreamFlush(comp->stream);
pthread_mutex_unlock(&comp->write_lock);
Free(c);
}

View file

@ -8,6 +8,8 @@
#include <Parsee.h>
#include <XML.h>
#include "XMPPThread/internal.h"
bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
@ -18,8 +20,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
return false;
}
pthread_mutex_lock(&jabber->write_lock);
iq_query = XMLCreateTag("iq");
query = XMLCreateTag("query");
@ -35,8 +35,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
XMLElement *identity;
XMLEncode(jabber->stream, iq_query);
StreamFlush(jabber->stream);
XMPPSendStanza(jabber, iq_query, 10000);
XMLFreeElement(iq_query);
/* Except an IQ reply. 10 seconds of timeout is pretty
@ -45,8 +44,8 @@ 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);
pthread_mutex_unlock(&jabber->write_lock);
return false;
}
query = XMLookForUnique(iq_query, "query");
@ -57,7 +56,11 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
"conference"))
{
XMLFreeElement(iq_query);
pthread_mutex_unlock(&jabber->write_lock);
Log(LOG_DEBUG, "MUC INFO ERROR");
Log(LOG_DEBUG,
"identityp=%p category=%s", identity,
identity ? HashMapGet(identity->attrs, "category") : NULL
);
return false;
}
@ -73,7 +76,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
XMLFreeElement(iq_query);
}
}
pthread_mutex_unlock(&jabber->write_lock);
return true;
}
@ -116,7 +118,6 @@ XMPPRequestVoice(XMPPComponent *jabber, char *from, char *muc)
return;
}
pthread_mutex_lock(&jabber->write_lock);
stanza = XMLCreateTag("message");
XMLAddAttr(stanza, "id", (identifier = StrRandom(32)));
XMLAddAttr(stanza, "from", from);
@ -160,10 +161,123 @@ XMPPRequestVoice(XMPPComponent *jabber, char *from, char *muc)
}
XMLAddChild(stanza, x);
}
XMLEncode(jabber->stream, stanza);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, stanza, 10000);
XMLFreeElement(stanza);
Free(identifier);
}
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)
{
return false;
}
presence = XMLCreateTag("presence");
x = XMLCreateTag("x");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc);
XMLAddAttr(presence, "id", (id = StrRandom(8)));
XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc");
history = XMLCreateTag("history");
if (time > 0)
{
stime = StrInt(time);
}
XMLAddAttr(history, "seconds", stime);
if (time > 0)
{
Free(stime);
stime = NULL;
}
XMLAddChild(x, history);
XMLAddChild(presence, x);
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)
{
x = XMLCreateTag("x");
XMLAddAttr(x, "xmlns", "vcard-temp:x:update");
photo = XMLCreateTag("photo");
XMLAddChild(photo, XMLCreateText(hash));
XMLAddChild(x, photo);
XMLAddChild(presence, x);
}
XMPPSendStanza(comp, presence, 10000);
XMLFreeElement(presence);
Free(from);
if (ret && (reply = ParseeAwaitStanza(id, 500)))
{
bool exit_code = true;
if (XMPPHasError(reply, "conflict"))
{
exit_code = false;
}
XMLFreeElement(reply);
Free(id);
return exit_code;
}
Free(id);
return true;
}
void
XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
{
XMLElement *presence;
IQFeatures *features;
char *from, *id;
if (!comp || !fr || !muc)
{
return;
}
presence = XMLCreateTag("presence");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc);
XMLAddAttr(presence, "id", (id = StrRandom(8)));
XMLAddAttr(presence, "type", "unavailable");
if (reason)
{
XMLElement *status = XMLCreateTag("status");
XMLElement *string = XMLCreateText(reason);
XMLAddChild(status, string);
XMLAddChild(presence, status);
}
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);
XMLFreeElement(presence);
Free(from);
Free(id);
}

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)
@ -66,103 +68,13 @@ XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
}
pthread_mutex_lock(&comp->write_lock);
XMLEncode(comp->stream, message);
StreamFlush(comp->stream);
XMPPSendStanza(comp, message, 10000);
XMLFreeElement(message);
pthread_mutex_unlock(&comp->write_lock);
Free(from);
Free(ident);
}
void
XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
{
XMLElement *presence;
char *from, *id;
if (!comp || !fr || !muc)
{
return;
}
pthread_mutex_lock(&comp->write_lock);
presence = XMLCreateTag("presence");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc);
XMLAddAttr(presence, "id", (id = StrRandom(8)));
XMLAddAttr(presence, "type", "unavailable");
if (reason)
{
XMLElement *status = XMLCreateTag("status");
XMLElement *string = XMLCreateText(reason);
XMLAddChild(status, string);
XMLAddChild(presence, status);
}
XMPPAnnotatePresence(presence);
XMLEncode(comp->stream, presence);
StreamFlush(comp->stream);
XMLFreeElement(presence);
Free(from);
Free(id);
pthread_mutex_unlock(&comp->write_lock);
}
bool
XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc)
{
XMLElement *presence, *x, *reply;
char *from, *id;
if (!comp || !fr || !muc)
{
return false;
}
pthread_mutex_lock(&comp->write_lock);
presence = XMLCreateTag("presence");
x = XMLCreateTag("x");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc);
XMLAddAttr(presence, "id", (id = StrRandom(8)));
XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc");
XMLAddChild(presence, x);
XMPPAnnotatePresence(presence);
XMLEncode(comp->stream, presence);
StreamFlush(comp->stream);
XMLFreeElement(presence);
Free(from);
pthread_mutex_unlock(&comp->write_lock);
/*if ((reply = ParseeAwaitStanza(id, 500)))
{
bool exit_code = true;
if (XMPPHasError(reply, "conflict"))
{
Log(LOG_WARNING, "UNIMPLEMENTED NAMING CONFLICT.");
exit_code = false;
}
XMLFreeElement(reply);
Free(id);
return exit_code;
}*/
(void) reply;
Free(id);
return true;
}
bool
XMPPIsParseeStanza(XMLElement *stanza)
{
@ -171,6 +83,8 @@ XMPPIsParseeStanza(XMLElement *stanza)
return false;
}
/* TODO: Check if the user is a trustworthy Parsee puppet instead of some
* guy sending random stanzas */
return !!XMLookForUnique(stanza, "x-parsee");
}
@ -247,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)
{
@ -321,35 +239,51 @@ XMPPHasError(XMLElement *stanza, char *type)
}
XMLElement *
XMPPSendDisco(XMPPComponent *jabber, char *from, char *to)
XMPPSendDisco(ParseeData *data, char *from, char *to)
{
XMPPComponent *jabber = data ? data->jabber : NULL;
XMLElement *ret, *iq;
char *identifier;
if (!jabber || !from || !to)
if (!data || !jabber || !from || !to)
{
return NULL;
}
pthread_mutex_lock(&data->oidl);
if ((ret = HashMapGet(data->oid_servers, to)))
{
ret = XMLCopy(ret);
pthread_mutex_unlock(&data->oidl);
return ret;
}
pthread_mutex_unlock(&data->oidl);
iq = XMLCreateTag("iq");
XMLAddAttr(iq, "type", "get");
XMLAddAttr(iq, "from", from);
XMLAddAttr(iq, "to", to);
XMLAddAttr(iq, "id", (identifier = StrRandom(69)));
XMLAddAttr(iq, "id", (identifier = StrRandom(64)));
{
XMLElement *query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "http://jabber.org/protocol/disco#info");
XMLAddChild(iq, query);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, iq, 10000);
XMLFreeElement(iq);
ret = ParseeAwaitStanza(identifier, 1.25 SECONDS);
Free(identifier);
if (ret)
{
/* TODO: On my own instance, disco replies are _really_ expensive.
* About 120KB. This is sad. Really sad. */
pthread_mutex_lock(&data->oidl);
XMLFreeElement(HashMapSet(data->oid_servers, to, ret));
ret = XMLCopy(ret);
pthread_mutex_unlock(&data->oidl);
}
return ret;
}
bool

View file

@ -34,7 +34,17 @@ struct XMPPCommandManager {
HashMap *sessions;
void *cookie;
XMPPCmdFilter filter;
};
static bool
XMPPDefaultFilter(XMPPCommandManager *manager, char *id, XMLElement *stanza)
{
(void) manager;
(void) stanza;
(void) id;
return true;
}
static void
XMPPDestroySession(XMPPSession *session)
{
@ -118,6 +128,7 @@ XMPPCreateManager(void *cookie)
ret->commands = HashMapCreate();
ret->sessions = HashMapCreate();
ret->cookie = cookie;
ret->filter = XMPPDefaultFilter;
return ret;
}
@ -161,12 +172,12 @@ XMPPFreeManager(XMPPCommandManager *manager)
Free(manager);
}
void
XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p)
XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s)
{
char *node_name;
XMPPCommand *val;
XMLElement *item;
if (!m || !p || !jid)
if (!m || !p || !jid || !s)
{
return;
}
@ -174,11 +185,14 @@ XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p)
pthread_mutex_lock(&m->lock);
while (HashMapIterate(m->commands, &node_name, (void **) &val))
{
item = XMLCreateTag("item");
XMLAddAttr(item, "jid", jid);
XMLAddAttr(item, "node", node_name);
XMLAddAttr(item, "name", XMPPGetCommandDesc(val));
XMLAddChild(p, item);
if (m->filter(m, node_name, s))
{
item = XMLCreateTag("item");
XMLAddAttr(item, "jid", jid);
XMLAddAttr(item, "node", node_name);
XMLAddAttr(item, "name", XMPPGetCommandDesc(val));
XMLAddChild(p, item);
}
}
pthread_mutex_unlock(&m->lock);
}
@ -206,6 +220,16 @@ XMPPVerifySession(XMPPCommandManager *mgr, char *s_id, char *from, char *to)
return ret;
}
void
XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter)
{
if (!manager || !filter)
{
return;
}
manager->filter = filter;
}
bool
XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
{
@ -242,7 +266,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
/* This is an execution. */
cmd = HashMapGet(m->commands, node);
if (!cmd)
if (!cmd || !m->filter(m, node, stanza))
{
/* TODO: Set an error note */
goto end;
@ -274,10 +298,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
x = XMPPFormifyCommand(m, cmd, from);
XMLAddChild(command_xml, x);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLFreeElement(iq);
goto end;
@ -305,10 +326,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, NULL);
XMLAddChild(iq, command_xml);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLFreeElement(iq);
InvalidateSession(m, session_id);
@ -348,10 +366,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, x_form);
XMLAddChild(iq, command_xml);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLFreeElement(iq);
InvalidateSession(m, session_given);

View file

@ -43,7 +43,6 @@ AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
ref = DbLock(data->db, 1, "admins");
admins = GrabArray(DbJson(ref), 1, "admins");
ArrayAdd(admins, JsonValueString(glob));
DbUnlock(data->db, ref);
}

View file

@ -15,19 +15,11 @@ void
AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
size_t i;
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
x = XMLCreateTag("x");
title = XMLCreateTag("title");
@ -58,4 +50,7 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
DbUnlock(data->db, ref);
}
XMLAddChild(out, x);
(void) form;
(void) from;
}

View file

@ -29,4 +29,6 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
ParseeCleanup(data);
/* TODO: Cleanup old sessions? */
SetNote("info", "Parsee data was sucessfully cleant up.");
(void) form;
}

View file

@ -92,7 +92,7 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
DbRef *admins;
Array *admin_list;
size_t i;
XMPPOption *admin_opt;
XMPPOption *admin_opt = NULL;
char *trimmed = ParseeTrimJID(from);
if (!ParseeIsAdmin(data, trimmed))
@ -104,14 +104,17 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
}
Free(trimmed);
admin_opt = XMPPCreateList(true, false, "glob", "[NVM!]");
admins = DbLock(data->db, 1, "admins");
admin_list = GrabArray(DbJson(admins), 1, "admins");
for (i = 0; i < ArraySize(admin_list); i++)
{
char *glob = JsonValueAsString(ArrayGet(admin_list, i));
if (!admin_opt)
{
admin_opt = XMPPCreateList(true, false, "glob", glob);
continue;
}
XMPPAddListOption(admin_opt, glob);
}

View file

@ -0,0 +1,49 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
void
MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
char *chat_id = ParseeGetFromMUCID(data, muc);
char *room_id = ParseeGetRoomID(data, chat_id);
char *msg = StrConcat(5,
"The MUC ", muc, " is bridged to ", room_id, "."
);
SetNote("info", msg);
Free(muc);
Free(msg);
Free(room_id);
Free(chat_id);
(void) form;
}
void
MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
char *chat_id = ParseeGetFromMUCID(data, muc);
char *msg = StrConcat(5,
"The MUC ", muc, "'s internal ID is ", chat_id, "."
);
SetNote("info", msg);
Free(muc);
Free(msg);
Free(chat_id);
(void) form;
}

117
src/XMPPCommands/MUCKV.c Normal file
View file

@ -0,0 +1,117 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
char *key = NULL, *val = NULL;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Setting MUC properties requires the 'owner' affiliation.");
Free(affiliation);
return;
}
GetFieldValue(key, "key", form);
GetFieldValue(val, "val", form);
if (!key || !val)
{
SetNote("error", "No keys or no value given.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
ParseeSetChatSetting(data, chat_id, key, val);
SetNote("info", "Set key-value pair!");
end:
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}
void
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
HashMap *settings = NULL;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Getting MUC roperties requires the 'owner' affiliation.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
settings = ParseeGetChatSettings(data, chat_id);
x = XMLCreateTag("x");
title = XMLCreateTag("title");
SetTitle(x, "MUC/room settings");
XMLAddChild(x, title);
XMLAddAttr(x, "xmlns", "jabber:x:data");
XMLAddAttr(x, "type", "result");
{
char *key, *val;
reported = XMLCreateTag("reported");
XMLAddChild(x, reported);
/* Report */
Report("key", "Setting's key");
Report("val", "Setting's value");
/* Set */
while (HashMapIterate(settings, &key, (void **) &val))
{
BeginItem();
SetField("key", key);
SetField("val", val);
EndItem();
}
}
XMLAddChild(out, x);
end:
ParseeFreeChatSettings(settings);
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}

View file

@ -0,0 +1,67 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *parsee;
char *room;
char *muc;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Unlinking a MUC requires the 'owner' affiliation.");
Free(affiliation);
return;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
room = ParseeGetRoomID(data, chat_id);
if (!chat_id)
{
SetNote("error", "Couldn't fetch chat ID.");
Free(muc);
return;
}
ParseeUnlinkRoom(data, chat_id);
parsee = ParseeMXID(data);
Free(ASSend(
data->config, room, parsee,
"m.room.message",
MatrixCreateNotice("This room has been unlinked."),
0
));
ASLeave(data->config, room, parsee);
XMPPLeaveMUC(data->jabber, "parsee", muc, "Unlinked by MUC admin.");
/* Setting an error here won't work, as we're communicating through
* the MUC, which we *left*. I guess we can try to defer the leave. */
SetNote("info", "Unlinked MUC.");
Free(affiliation);
Free(chat_id);
Free(parsee);
Free(room);
Free(muc);
(void) form;
}

View file

@ -87,4 +87,6 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
}
DbUnlock(data->db, ref);
}
(void) form;
}

View file

@ -14,7 +14,6 @@
void
StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
size_t alloc = MemoryAllocated();
size_t kb = alloc >> 10;
@ -26,13 +25,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLElement *x;
XMLElement *title, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
x = XMLCreateTag("x");
title = XMLCreateTag("title");
@ -75,4 +67,7 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
EndItem();
}
XMLAddChild(out, x);
(void) form;
(void) m;
}

View file

@ -0,0 +1,142 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
void
ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
if (!DbDelete(data->db, 1, "whitelist"))
{
SetNote("error", "Parsee whitelist was non-existent or could not be removed.");
return;
}
/* TODO: Cleanup old sessions? */
SetNote("info", "Parsee whitelist was removed.");
(void) form;
}
void
AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
char *entity = NULL;
DbRef *ref;
GetFieldValue(entity, "entity", form);
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
if (!entity)
{
SetNote("error", "No entity found.");
Free(trimmed);
return;
}
Free(trimmed);
ref = DbLock(data->db, 1, "whitelist");
if (!ref)
{
ref = DbCreate(data->db, 1, "whitelist");
}
if (!ref)
{
SetNote("error", "Couldn't get a database entry. You're cooked.");
return;
}
JsonValueFree(HashMapSet(
DbJson(ref),
entity, JsonValueObject(HashMapCreate())
));
DbUnlock(data->db, ref);
SetNote("info", "Server successfully whitelisted.");
}
void
WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
x = XMLCreateTag("x");
XMLAddAttr(x, "xmlns", "jabber:x:data");
title = XMLCreateTag("title");
XMLAddChild(x, title);
XMLAddChild(out, x);
Free(trimmed);
SetTitle(x, NAME " chat whitelist");
XMLAddAttr(x, "type", "result");
{
DbRef *ref = DbLock(data->db, 1, "whitelist");
HashMap *obj;
char *serv;
JsonValue *obj_val;
reported = XMLCreateTag("reported");
XMLAddChild(x, reported);
if (!ref)
{
ref = DbCreate(data->db, 1, "global_bans");
}
obj = DbJson(ref);
/* Report */
Report("server", "Allowed servers");
/* Set */
while (HashMapIterate(obj, &serv, (void **) &obj_val))
{
BeginItem();
SetField("server", serv);
EndItem();
(void) obj_val;
}
DbUnlock(data->db, ref);
}
(void) form;
}

View file

@ -3,7 +3,6 @@
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Sha.h>
#include <Parsee.h>
@ -119,13 +118,12 @@ ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza)
return ret;
}
/* TODO: Cache this information. */
bool
ServerHasXEP421(ParseeData *data, char *from)
{
char *server = NULL, *parsee;
char *server = NULL, *postserv, *parsee;
XMLElement *disco;
bool ret;
bool ret = false;
if (!data || !from)
{
return false;
@ -140,15 +138,16 @@ ServerHasXEP421(ParseeData *data, char *from)
{
server++;
}
server = StrDuplicate(server);
if (strchr(server, '/'))
server = StrDuplicate(server);
postserv = server ? strchr(server, '/') : NULL;
if (postserv)
{
*(strchr(server, '/')) = '\0';
*postserv = '\0';
}
parsee = ParseeJID(data);
disco = XMPPSendDisco(data->jabber, parsee, server);
disco = XMPPSendDisco(data, parsee, server);
ret = XMPPDiscoAdvertises(disco, "urn:xmpp:occupant-id:0");
@ -163,10 +162,9 @@ ServerHasXEP421(ParseeData *data, char *from)
* into a SHA-256 value
* > "The recipient MUST consider the occupant identifier to be an opaque
* > string.". */
static char *
char *
ScrambleOID(ParseeData *data, char *opaque_oid)
{
unsigned char *raw;
char *sha, *mxid;
const ParseeConfig *c = data->config;
if (!opaque_oid)
@ -175,9 +173,7 @@ ScrambleOID(ParseeData *data, char *opaque_oid)
}
/* Turns this into a 128-byte long, Matrix-safe value. */
raw = Sha256(opaque_oid);
sha = ShaToHex(raw);
Free(raw);
sha = ParseeSHA256(opaque_oid);
/* TODO: Either mark this specially, or drop Parsee JID flags
* altogether before any 1.0.0 */

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;
}

Some files were not shown because too many files have changed in this diff Show more