Compare commits

..

No commits in common. "master" and "lmdb" have entirely different histories.

133 changed files with 2915 additions and 8900 deletions

View file

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

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

View file

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

View file

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

View file

@ -1,14 +0,0 @@
# 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,3 +1,7 @@
Some dates for Parsee-related events. They mostly serve as LDA's TODOs with
a strict deadline:
- Get star-of-hope out by November 8
- ~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.

View file

@ -1,6 +1,6 @@
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)
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.
As Parsee depends on Cytoplasm, its license is left here in COPYING.CYTO

89
Makefile Normal file
View file

@ -0,0 +1,89 @@
# 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,54 +1,39 @@
# 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.
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.
Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is NOT a drop-in replacment.
## Why?
### Naming
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.
The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi), a
"*bridge* princess".
### 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*,
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))
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.)
### "Why not just use Matrix lol"
### "Why not just use XMPP lol"
These two having the same answer should be enough information.
These two having the same answer should be enough information. Also can I *just* have fun?
One could also argue that both sides need to migrate(onboard) the other side, so
a bridge may be a good way to start.
## BUILDING
```sh
$ 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
$ 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.
```
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` for Cytoplasm's include and
library paths specifically.)
If you build with MbedTLS, please mind setting the `CYTO_TLS_CA` env to Parsee.
change a few variables (you can set `CYTO_INC` and `CYTO_LIB`)
### 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 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
```
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).
## RUNNING
First off, you may want to configure Parsee by running the `config` tool(generally named
@ -61,8 +46,7 @@ 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 \
-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.
-p 5347
```
If everything goes well, it should generate a `parsee.json` file.
@ -75,44 +59,31 @@ 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 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.
## TODOS
- Add [libomemo](https://github.com/gkdr/libomemo) 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~~ 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.
put in the work of either forking off libolm or making a binding to KappaChat.
- 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
@ -129,7 +100,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 there. If you *really* want, you may just open
Please avoid asking for help/issues here. 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,19 +1,6 @@
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.
@ -21,14 +8,21 @@ 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
TODO: Add support from Matrix.
HALF-IMPLEMENTED: Removing reacts won't work.
~ 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-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
~ 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.
For future XEPs:
- https://xmpp.org/extensions/xep-0449.html
@ -38,16 +32,22 @@ 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:
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
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
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).
- 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

View file

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

View file

@ -1,727 +0,0 @@
/* 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

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -1,5 +0,0 @@
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

View file

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

971
src/AS.c
View file

@ -55,5 +55,976 @@ 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);
}

View file

@ -1,93 +0,0 @@
#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;
}

View file

@ -1,120 +0,0 @@
#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);
}

View file

@ -1,220 +0,0 @@
#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;
}

View file

@ -1,42 +0,0 @@
#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;
}

View file

@ -1,206 +0,0 @@
#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;
}

View file

@ -1,46 +0,0 @@
#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;
}

View file

@ -1,82 +0,0 @@
#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);
}

View file

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

View file

@ -1,79 +0,0 @@
#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;
}

View file

@ -1,37 +0,0 @@
#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 || (cmd > end_data))
if (!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 = '\0';
char char_type;
switch (state)
{
case STATE_WHITE:
if (!isblank((int) c))
if (!isblank(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((int) c)))
if ((type && c == char_type) || (!type && isblank(c)))
{
break;
}

View file

@ -15,47 +15,35 @@ CommandCreateRouter(void)
void
CommandAddCommand(CommandRouter *rter, char *c, CommandRoute rte)
{
CommandRoute *indirect;
if (!rter || !c || !rte)
{
return;
}
/* 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);
HashMapSet(rter->routes, c, rte);
}
void
RouteCommand(CommandRouter *rter, Command *cmd, void *d)
{
CommandRoute *route;
CommandRoute route;
if (!rter || !cmd)
{
return;
}
route = HashMapGet(rter->routes, cmd->command);
if (route && *route)
if (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,28 +22,11 @@ CommandHead(CmdBanUser, cmd, argp)
BotDestroy();
return;
}
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);
ReplySprintf("Banning %s from '%s'...",
user, room
);
BotDestroy();
}

View file

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

View file

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

View file

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

View file

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

View file

@ -9,60 +9,43 @@
#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 *event = args->event;
HashMap *json, *event = args->event, *mucs;
DbRef *ref;
char *muc = NULL, *chat_id = NULL, *room = NULL;
BotInitialise();
if (!Grab(data, cmd, &muc, &chat_id, &room))
muc = HashMapGet(cmd->arguments, "muc");
if (!muc)
{
ReplyBasic("`muc`|`room` REQUIRED");
ReplyBasic("`muc` field REQUIRED.");
goto end;
}
chat_id = ParseeGetFromMUCID(data, muc);
room = ParseeGetRoomID(data, chat_id);
ref = DbLock(data->db, 1, "chats");
json = DbJson(ref);
chat_id = StrDuplicate(GrabString(json, 2, "mucs", muc));
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);
ParseeUnlinkRoom(data, chat_id);
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);
/* TODO: Do it automatically, if *not plumbed* */
ReplySprintf("The MUC %s is now *unlinked*.", muc);

View file

@ -37,10 +37,12 @@ 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);
@ -79,7 +81,7 @@ MatrixCreateNickChange(char *nick)
return map;
}
HashMap *
MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info)
MatrixCreateMedia(char *mxc, char *body, char *mime)
{
HashMap *map;
char *mime_type = NULL, *matrix_type = NULL;
@ -91,10 +93,9 @@ MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info)
matrix_type = "m.file";
if (mime)
{
size_t i, len;
size_t i;
mime_type = StrDuplicate(mime);
len = strlen(mime);
for (i = 0; i < len; i++)
for (i = 0; i < strlen(mime); i++)
{
if (mime_type[i] == '/')
{
@ -120,12 +121,6 @@ MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info)
}
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));
@ -187,18 +182,6 @@ 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 *

View file

@ -1,68 +0,0 @@
#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,6 +19,7 @@ GlobMatches(char *rule, char *string)
switch (c1)
{
case '*':
/* TODO */
while ((c2 = *string) && (c2 != next))
{
string++;

View file

@ -29,18 +29,19 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
arg.stream = stream;
Log(LOG_DEBUG, "%s %s",
Log(LOG_NOTICE, "%s %s",
HttpRequestMethodToString(HttpRequestMethodGet(ctx)),
path
);
if (!HttpRouterRoute(data->router, path, &arg, (void **) &response))
{
Log(LOG_DEBUG, "Couldn't route %s", path);
Log(LOG_NOTICE, "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. */
@ -55,11 +56,8 @@ 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 *
@ -73,7 +71,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path
ctx = HttpRequest(
meth,
conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE,
HTTP_FLAG_TLS,
conf->homeserver_port, conf->homeserver_host,
path
);

View file

@ -1,6 +1,5 @@
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/Platform.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Cron.h>
@ -13,7 +12,6 @@
#include <signal.h>
#include <stdlib.h>
#include <StanzaBuilder.h>
#include <Parsee.h>
#include <XMPP.h>
#include <AS.h>
@ -29,79 +27,6 @@ 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)
{
@ -110,11 +35,6 @@ 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));
@ -122,32 +42,24 @@ Main(Array *args, HashMap *env)
"%s - v%s[%s] (Cytoplasm %s)",
NAME, VERSION, CODE, CytoplasmGetVersionStr()
);
ParseePrintASCII();
Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
#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());
ParseeConfigLoad("parsee.json");
ParseeConfigInit();
parsee_conf = ParseeConfigGet();
{
ArgParseState state;
char *opts = ParseeGenerateGetopt(arguments);
int flag;
int xmpp = 8;
int http = 8;
ArgParseStateInit(&state);
while ((flag = ArgParse(&state, args, opts)) != -1)
while ((flag = ArgParse(&state, args, "gH:J:")) != -1)
{
switch (flag)
{
case 'h':
ParseeGenerateHelp(arguments);
Free(opts);
goto end;
case 'H':
http = strtol(state.optArg, NULL, 10);
break;
@ -158,155 +70,46 @@ 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;
}
}
Free(opts);
ParseeSetThreads(xmpp, http);
}
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();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising nick table");
}
ParseeInitialiseNickTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising affiliation table");
}
ParseeInitialiseAffiliationTable();
Log(LOG_NOTICE, "Setting up local Matrix user...");
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
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);
CronEvery(cron, 10 SECONDS, ParseeCheckMatrix, conf.handlerArgs);
ParseeCleanup(conf.handlerArgs);
cron = CronCreate( 10 SECONDS );
CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs);
CronStart(cron);
@ -318,9 +121,8 @@ Main(Array *args, HashMap *env)
}
server = HttpServerCreate(&conf);
((ParseeData *) conf.handlerArgs)->server = server;
if (!ParseeInitialiseSignals(conf.handlerArgs, xmpp_thr))
if (!ParseeInitialiseSignals(server, xmpp_thr, jabber))
{
goto end;
}
@ -344,12 +146,8 @@ end:
CronStop(cron);
CronFree(cron);
ParseeFreeData(conf.handlerArgs);
ParseeDestroyAffiliationTable();
ParseeDestroyNickTable();
ParseeDestroyOIDTable();
ParseeDestroyHeadTable();
ParseeDestroyJIDTable();
(void) env;
return 0;
}

View file

@ -8,56 +8,9 @@
#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)
{
@ -98,95 +51,20 @@ 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 *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);
char *rev = StrConcat(2, muc, "/parsee");
Free(jabber);
Free(avatar);
Free(name);
XMPPJoinMUC(data->jabber, jid, rev);
Free(rev);
Free(muc);
avatar = NULL;
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */
}
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);
Free(chat_id);
}
else if ((StrEquals(membership, "leave") ||
StrEquals(membership, "ban"))
@ -204,29 +82,14 @@ 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;
}
name = StrDuplicate(ParseeLookupNick(muc_id, sender));
rev = StrConcat(3, muc_id, "/", name);
/* TODO: Check the name's validity */
name = ASGetName(data->config, room_id, state_key);
rev = StrConcat(4, muc_id, "/", name, "[p]");
XMPPLeaveMUC(jabber, jid, rev, reason);
ParseePushNickTable(muc_id, sender, NULL);
end:
Free(chat_id);
Free(muc_id);
@ -255,9 +118,14 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
return;
}
if (!body || *body != '!')
if (*body != '!')
{
/* All commands are to be marked with a ! */
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("Please enter a valid command")
));
Free(profile);
return;
}
@ -267,7 +135,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("You are not authorised to do this."), 0
MatrixCreateNotice("You are not authorised to do this.")
));
Free(profile);
return;
@ -289,10 +157,12 @@ 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 = NULL;
char *user;
DbRef *room_data = NULL;
HashMap *data_json = NULL;
DbRef *room_data;
HashMap *data_json;
XMPPComponent *jabber = data ? data->jabber : NULL;
bool direct = false;
if (!data || !event || !from || !to)
@ -324,8 +194,7 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
else
{
char *matrix_name = NULL, *matrix_avatar = NULL;
char *mime = NULL, *sha = NULL;
char *matrix_name, *muc_join_as;
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
@ -339,16 +208,18 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
matrix_name = ASGetName(data->config, room_id, matrix_sender);
matrix_avatar = ASGetAvatar(data->config, NULL, 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);
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(mime);
Free(sha);
Free(muc_join_as);
}
Free(chat_id);
@ -358,47 +229,27 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
static void
ParseeMessageHandler(ParseeData *data, HashMap *event)
{
if (!data || !event)
{
return;
}
XMPPComponent *jabber = data->jabber;
StanzaBuilder *builder = NULL;
StanzaBuilder *builder;
DbRef *ref = NULL;
HashMap *json = NULL;
HashMap *json;
char *msgtype = GrabString(event, 2, "content", "msgtype");
char *m_sender = GrabString(event, 1, "sender");
char *unedited_id = NULL;
char *body = GrabString(event, 2, "content", "body");
char *id = GrabString(event, 1, "room_id");
char *ev_id = GrabString(event, 1, "event_id");
char *chat_id = NULL, *muc_id = NULL;
char *m_sender = GrabString(event, 1, "sender");
char *chat_id, *muc_id;
char *reply_id = MatrixGetReply(event);
char *xepd = ParseeXMPPify(data, event);
char *xepd = ParseeXMPPify(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))
{
@ -410,6 +261,9 @@ 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"));
@ -429,44 +283,38 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
type = direct ? "chat" : "groupchat";
user = GrabString(json, 1, "xmpp_user");
unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename"));
encoded_from = ParseeEncodeMXID(m_sender);
xmppified_user = StrConcat(3,
encoded_from, "@", jabber->host
);
unauth = ParseeToUnauth(data, url);
if (direct)
{
xmppified_user = ParseeEncodeMXID(m_sender);
to = StrDuplicate(user);
Free(chat_id);
}
else
{
char *name, *mime = NULL, *sha = NULL;
char *avatar;
char *name, *rev;
/* Try to find the chat ID */
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
goto end;
}
xmppified_user = ParseeEncodeMXID(m_sender);
/* TODO: Avoid using the AS endpoints */
/* 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?" */
name = ASGetName(data->config, id, m_sender);
avatar = ASGetAvatar(data->config, NULL, m_sender);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
rev = StrConcat(4, muc_id, "/", name, "[p]");
Free(JoinMUC(data, event, encoded_from, muc_id, name, sha));
XMPPJoinMUC(jabber, xmppified_user, rev);
to = muc_id;
Free(sha);
Free(mime);
Free(name);
Free(avatar);
Free(rev);
}
if (reply_id)
{
/* TODO: Monocles chat DM users HATE this trick!
@ -494,13 +342,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *xmpp_ident = StrRandom(32);
builder = CreateStanzaBuilder(xmppified_user, to, xmpp_ident);
SetStanzaType(builder, type);
SetStanzaBody(builder, unauth ? unauth : (xepd ? xepd : body));
SetStanzaBody(builder, xepd ? xepd : body);
SetStanzaReply(builder, stanza, sender);
SetStanzaLink(builder, unauth);
SetStanzaEdit(builder, origin_id);
SetStanzaXParsee(builder, event);
WriteoutStanza(builder, jabber, data->config->max_stanza_size);
WriteoutStanza(builder, jabber);
DestroyStanzaBuilder(builder);
if (direct)
@ -523,7 +371,6 @@ end:
Free(stanza);
Free(sender);
Free(unauth);
Free(encoded_from);
Free(unedited_id);
DbUnlock(data->db, ref);
@ -555,7 +402,8 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
return;
}
else if (StrEquals(event_type, "m.room.message") ||
StrEquals(event_type, "m.sticker"))
StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker
* support here... */
{
ParseeMessageHandler(data, event);
Free(parsee);

View file

@ -1,68 +0,0 @@
#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,15 +11,210 @@
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;
}
Log(LOG_ERR, "No config file found.");
Log(LOG_ERR, "Please use parsee-config to initialise %s.", NAME);
/* 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);
}
void
ParseeConfigLoad(char *conf)
@ -30,18 +225,12 @@ ParseeConfigLoad(char *conf)
{
return;
}
stream = StreamOpen(conf ? conf : "parsee.json", "r");
stream = StreamOpen("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( \
@ -50,9 +239,6 @@ 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;
@ -67,25 +253,10 @@ 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");
@ -101,7 +272,6 @@ ParseeSetThreads(int xmpp, int http)
{
if (!config)
{
Achievement("THREAD COUNT REQUEST WITHOUT CONFIG", true);
return;
}
config->http_threads = http;
@ -113,7 +283,6 @@ ParseeExportConfigYAML(Stream *stream)
{
if (!stream || !config)
{
Achievement("YAML EXPORT REQUEST WITHOUT CONFIG", true);
return;
}
StreamPrintf(stream, "# Autogenerated YAML AS entry for %s\n", NAME);
@ -127,7 +296,6 @@ 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");
@ -136,7 +304,6 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, " aliases:\n");
StreamPrintf(stream, " - exclusive: true\n");
StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base);
StreamFlush(stream);
}
void
@ -147,7 +314,6 @@ 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,18 +6,17 @@
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <Routes.h>
#include <Glob.h>
#include <AS.h>
ParseeData *
ParseeInitData(XMPPComponent *comp)
{
char *version;
ParseeData *data;
DbRef *ref;
if (!ParseeConfigGet())
{
return NULL;
@ -27,15 +26,8 @@ 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);
@ -43,47 +35,10 @@ ParseeInitData(XMPPComponent *comp)
if (!data->db)
{
Log(LOG_WARNING, "LMDB doesn't seem to be setup.");
if (data->config->db_size)
{
Log(LOG_WARNING, "Falling back to flat-file.");
}
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))\
{\
@ -101,23 +56,12 @@ 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);
@ -133,6 +77,8 @@ 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++)
@ -182,12 +128,9 @@ ParseeCleanup(void *datp)
} \
while (0)
/* 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 */
CleanupField(stanza, 30 MINUTES, 50);
CleanupField(event, 30 MINUTES, 50);
CleanupField(id, 30 MINUTES, 50);
#undef CleanupField
}
DbListFree(chats);
@ -238,15 +181,252 @@ ParseeCleanup(void *datp)
} \
while (0)
CleanupField(stanza, 3 HOURS, 500);
CleanupField(event, 3 HOURS, 500);
CleanupField(id, 3 HOURS, 500);
CleanupField(stanza, 3 HOURS, 50);
CleanupField(event, 3 HOURS, 50);
CleanupField(id, 3 HOURS, 50);
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)
{
@ -339,15 +519,6 @@ 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);
@ -550,178 +721,140 @@ end:
return ret;
}
void
ParseeUnlinkRoom(ParseeData *data, char *chat_id)
{
char *muc, *room;
DbRef *ref;
if (!data || !chat_id)
{
return;
}
muc = ParseeGetMUCID(data, chat_id);
room = ParseeGetRoomID(data, chat_id);
if (!muc || !room)
{
Free(muc);
Free(room);
return;
}
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
ParseeIsMUCWhitelisted(ParseeData *data, char *muc)
{
char *server, *serv_start, *postserv;
DbRef *ref;
bool ret;
if (!data || !muc)
{
return false;
}
if (!DbExists(data->db, 1, "whitelist"))
{
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';
}
ref = DbLockIntent(data->db,
DB_HINT_READONLY,
1, "whitelist"
);
ret = HashMapGet(DbJson(ref), server);
DbUnlock(data->db, ref);
Free(server);
return ret;
}
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat)
{
HashMap *ret, *json;
DbRef *ref;
char *key;
JsonValue *value;
if (!data || !chat)
{
return NULL;
}
ref = DbLockIntent(data->db, DB_HINT_READONLY,
3, "chats", chat, "settings"
);
json = DbJson(ref);
if (!ref)
{
return HashMapCreate();
}
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)
ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
{
DbRef *ref;
HashMap *json;
if (!data || !chat || !key || !val)
HashMap *j, *obj;
if (!data || !glob)
{
return;
}
ref = DbLockIntent(data->db, DB_HINT_WRITE,
3, "chats", chat, "settings"
);
ref = DbLock(data->db, 1, "global_bans");
if (!ref)
{
ref = DbCreate(data->db, 3, "chats", chat, "settings");
ref = DbCreate(data->db, 1, "global_bans");
}
json = DbJson(ref);
JsonValueFree(HashMapSet(json, key, JsonValueString(val)));
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);
return;
}
bool
ParseeIsMediaEnabled(ParseeData *data, char *chat_id)
ParseeManageBan(ParseeData *data, char *user, char *room)
{
char *value;
bool ret;
if (!data || !chat_id)
DbRef *ref;
HashMap *j;
char *key;
JsonValue *val;
bool banned = false , matches = false;
if (!data || !user)
{
return false;
}
ret = !StrEquals(
(value = ParseeGetChatSetting(data, chat_id, "p.media.enabled")),
"false"
);
Free(value);
return ret;
ref = DbLock(data->db, 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;
}
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;
}
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();
}
}
char *
ParseeGenerateMTO(char *common_id)
{
char *matrix_to;
if (!common_id)
{
return NULL;
}
common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", common_id);
Free(common_id);
return matrix_to;
}

View file

@ -1,74 +0,0 @@
/* 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;
}

194
src/Parsee/JIDTable.c Normal file
View file

@ -0,0 +1,194 @@
#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);
}

View file

@ -1,25 +0,0 @@
#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]);
}
}

View file

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

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

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

@ -1,72 +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);
}

View file

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

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

View file

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

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

@ -1,323 +0,0 @@
#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;
}

View file

@ -1,97 +0,0 @@
#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;
}

View file

@ -1,95 +0,0 @@
#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;
}

View file

@ -1,53 +0,0 @@
#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,80 +2,37 @@
#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, *params;
HashMap *reqh;
char *server = ArrayGet(arr, 0);
char *identi = ArrayGet(arr, 1);
char *key, *val;
char *hmac, *chkmak = NULL;
char *path, *key, *val;
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. */
/* TODO: Make it check the DB for its validicity. "Purging" would be useful.
*/
if (!server || !identi)
{
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);
return MatrixCreateError("M_NOT_YET_UPLOADED", "No server/identifier");
}
Free(chkmak);
/* Proxy the media through an authenticated endpoint if the HMAC
* is valid. */
cctx = TryDownload(args->data, server, identi);
server = HttpUrlEncode(server);
identi = HttpUrlEncode(identi);
path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi);
cctx = ParseeCreateRequest(args->data->config, HTTP_GET, path);
ASAuthenticateRequest(args->data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
reqh = HttpResponseHeaders(cctx);
while (HashMapIterate(reqh, &key, (void **) &val))
{
@ -88,6 +45,8 @@ RouteHead(RouteMedia, arr, argp)
}
HttpClientContextFree(cctx);
Free(server);
Free(identi);
return NULL;
}

View file

@ -25,12 +25,13 @@ 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,26 +41,13 @@ 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!",
"'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 !"
"Throw jabs!"
};
const size_t count = sizeof(quotes)/sizeof(*quotes);
@ -82,7 +69,6 @@ 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 {");
@ -90,17 +76,6 @@ 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;");
@ -123,25 +98,15 @@ RouteHead(RouteRoot, arr, argp)
P("<body>");
{
size_t i;
P("<center>");
P("<h1>Your %s is running, all with that %s!</h1>", NAME, CODE);
P("</center>");
P("<center><h1>");
P("Your %s is running, all with that %s!", NAME, CODE);
P("</h1></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>");
{
@ -179,7 +144,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>.");
}
@ -234,7 +199,7 @@ RouteHead(RouteRoot, arr, argp)
P("Some clicky links relating to %s:", NAME);
P("<ul>");
{
const char *fedi = "https://tengu.kappach.at/parsee";
const char *fedi = "https://ak.ari.lt/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);
@ -254,6 +219,5 @@ RouteHead(RouteRoot, arr, argp)
P("</html>");
#undef P
(void) arr;
return NULL;
}

View file

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

View file

@ -67,8 +67,7 @@ RouteHead(RouteRoomAck, arr, argp)
}
muc = ParseeDecodeLocalMUC(args->data->config, room);
if (ParseeManageBan(args->data, muc, NULL) ||
ParseeIsMUCWhitelisted(args->data, muc))
if (ParseeManageBan(args->data, muc, NULL))
{
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError(
@ -131,7 +130,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, NULL, -1, false);
XMPPJoinMUC(args->data->jabber, "parsee", rev);
Free(rev);
}

View file

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

View file

@ -40,7 +40,6 @@ 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;
@ -185,14 +184,14 @@ ExportStanza(StanzaBuilder *builder)
builder->replying_to_sender &&
builder->body)
{
int off = ParseeFindDatastartU(builder->body);
int off = ParseeFindDatastart(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_sender);
XMLAddAttr(reply, "id", builder->replying_to_stanza);
XMLAddAttr(reply, "to", builder->replying_to_stanza);
XMLAddAttr(reply, "id", builder->replying_to_sender);
XMLAddAttr(reply, "xmlns", "urn:xmpp:reply:0");
XMLAddAttr(fallback, "xmlns", "urn:xmpp:fallback:0");
@ -228,21 +227,22 @@ ExportStanza(StanzaBuilder *builder)
}
void
WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber, size_t max)
WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber)
{
XMLElement *elem;
if (!builder || !jabber)
{
return;
}
if (!max)
{
max = 10000; /* XMPP recommended limit */
}
elem = ExportStanza(builder);
XMPPSendStanza(jabber, elem, max);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, elem);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(elem);
return;
}
StanzaBuilder *
@ -258,6 +258,7 @@ 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");
@ -284,6 +285,16 @@ 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,6 +7,7 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
char **
StrSplitLines(char *text)
@ -116,7 +117,7 @@ StrFullRect(char **split)
char
StrGet(StringRect *rect, int line, int col)
{
size_t actual_line, actual_col;
int actual_line, actual_col;
char *linep;
if (!rect || !rect->source_lines)
{
@ -149,7 +150,7 @@ StrGet(StringRect *rect, int line, int col)
size_t
StrViewChars(StringRect rect, int line)
{
size_t actual_line;
int actual_line;
char *linep;
if (!rect.source_lines)
{
@ -173,7 +174,7 @@ StrViewChars(StringRect rect, int line)
StringRect
StrGetl(StringRect *rect, int line, bool extend)
{
size_t actual_line;
int actual_line;
StringRect ret;
if (!rect->source_lines)
{
@ -203,7 +204,7 @@ StrGetl(StringRect *rect, int line, bool extend)
StringRect
StrShift(StringRect rect, int n)
{
size_t new = rect.start_char + n;
int new = rect.start_char + n;
if (new > rect.end_char)
{
new = rect.end_char;

View file

@ -7,13 +7,125 @@
#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)
{
if (!buffer || n < 0)
Io *raw_io;
ReaderCookie *cookie;
if (!buffer)
{
return NULL;
}
return StreamFile(fmemopen(buffer, n ? (size_t) n : strlen(buffer), "rb"));
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);
}

View file

@ -11,9 +11,6 @@ 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
@ -36,9 +33,6 @@ 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;
}
@ -46,11 +40,10 @@ static int
CloseStreamWriter(void *coop)
{
/* Nothing to free as of now. */
(void) coop;
return 0;
}
static const IoFunctions Functions = {
const static IoFunctions Functions = {
.read = ReadStreamWriter,
.seek = SeekStreamWriter,
.write = WriteStreamWriter,

View file

@ -1,314 +0,0 @@
#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,14 +80,7 @@ DecodeQuote(StringRect rect, size_t *skip)
ret = rect;
ret.end_line--;
/* 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))
while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace(ch))
{
shift_by++;
}
@ -132,7 +125,7 @@ DecodeSpan(StringRect rect, char del, size_t *skip)
{
return StrFullRect(NULL);
}
if (!ret.source_lines && isspace((int) c))
if (!ret.source_lines && isspace(c))
{
return StrFullRect(NULL);
}
@ -329,8 +322,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
break;
case XEP393_MONO:
head = XMLCreateTag("code");
XMLAddChild(xmlparent, XMLCreateText("`"));
XMLAddChild(xmlparent, head);
XMLAddChild(head, XMLCreateText("`"));
break;
case XEP393_SRKE:
head = XMLCreateTag("s");
@ -372,7 +365,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
XMLAddChild(head, XMLCreateText("_"));
break;
case XEP393_MONO:
XMLAddChild(xmlparent, XMLCreateText("`"));
XMLAddChild(head, XMLCreateText("`"));
break;
case XEP393_SRKE:
XMLAddChild(head, XMLCreateText("~"));
@ -399,37 +392,21 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
char *
XEP393ToXMLString(XEP393Element *xepd)
{
XMLElement *root, *act_root;
XMLElement *child;
XMLElement *root;
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);
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);
XMLEncode(writer, root);
XMLFreeElement(root);
StreamFlush(writer);
StreamClose(writer);

View file

@ -31,12 +31,6 @@ 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);
@ -120,19 +114,9 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
void
XMLEncodeString(Stream *stream, char *data)
{
size_t i, len;
size_t 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++)
for (i = 0; i < strlen(data); i++)
{
char c = data[i];
if (c == '<')
@ -161,9 +145,6 @@ 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,7 +58,6 @@ 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);
@ -199,9 +198,7 @@ XMLCrank(XMLexer *lexer)
else if (XMLookahead(lexer, "--", false))
{
/* Throw error */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
return NULL;
}
break;
case XML_STATE_PI:
@ -218,9 +215,6 @@ XMLCrank(XMLexer *lexer)
if (!attrname)
{
/* TODO: Throw error */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
XMLPushElement(lexer, attrname);
@ -247,10 +241,7 @@ XMLCrank(XMLexer *lexer)
}
else if (XMLookahead(lexer, "'", true))
{
//while (true); uh oh
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
while (true);
}
break;
case XML_STATE_ATTRTAIL:
@ -259,8 +250,6 @@ XMLCrank(XMLexer *lexer)
if (!XMLookahead(lexer, ">", true))
{
/* TODO: Throw error. */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
lexer->state = XML_STATE_NONE;
@ -269,8 +258,6 @@ XMLCrank(XMLexer *lexer)
break;
default:
/* TODO */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
/* TODO: Crank our XML parser. */
@ -308,7 +295,7 @@ static bool
XMLookahead(XMLexer *lexer, const char *str, bool skip)
{
int *stack;
size_t top, i, len;
size_t top, i;
ssize_t ntop;
bool ret = false;
if (!lexer || !str)
@ -317,10 +304,9 @@ XMLookahead(XMLexer *lexer, const char *str, bool skip)
}
top = 0;
len = strlen(str);
stack = Malloc(len * sizeof(*stack));
stack = Malloc(strlen(str) * sizeof(*stack));
for (i = 0; i < len; i++)
for (i = 0; i < strlen(str); i++)
{
char c = str[i];
int getc = XMLGetc(lexer);
@ -350,8 +336,8 @@ seekback:
return ret;
}
#define IsNamestart(c) ((c == ':') || isalpha((int) c) || (c == '_'))
#define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit((int) c))
#define IsNamestart(c) ((c == ':') || isalpha(c) || (c == '_'))
#define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit(c))
static char *
XMLParseName(XMLexer *lexer)
{
@ -596,8 +582,6 @@ XMLCreateEnd(XMLexer *lexer, char *end)
event->col = 0;
event->offset = 0;
(void) lexer;
return event;
}
static XMLEvent *
@ -706,26 +690,6 @@ 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,15 +11,13 @@
#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 *addr, char *host, int port)
XMPPInitialiseCompStream(char *host, int port)
{
int sd = -1;
struct addrinfo hints, *res, *res0;
@ -28,26 +26,12 @@ XMPPInitialiseCompStream(char *addr, 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(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;
}
error = getaddrinfo(host, serv, &hints, &res0);
for (res = res0; res; res = res->ai_next)
{
@ -61,10 +45,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, strerror(errno)
);
close(sd);
sd = -1;
continue;
@ -75,10 +55,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
if (sd < 0)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': no socket available", __func__,
host
);
return NULL;
}
freeaddrinfo(res0);
@ -86,10 +62,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port)
stream = StreamFd(sd);
if (!stream)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, "couldn't create a Cytoplasm stream"
);
close(sd);
return NULL;
}
@ -104,15 +76,19 @@ XMPPInitialiseCompStream(char *addr, 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);
sha = ParseeSHA1(source);
raw_sha = Sha1(source);
sha = ShaToHex(raw_sha);
Free(raw_sha);
Free(source);
return sha;
@ -120,6 +96,7 @@ ComputeHandshake(char *shared, char *stream)
bool
XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
{
/* TODO */
XMLexer *sax;
XMLEvent *ev;
char *stream_id, *handshake;
@ -152,7 +129,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
}
break;
}
if (!ev || ev->type != XML_LEXER_STARTELEM ||
if (ev->type != XML_LEXER_STARTELEM ||
!StrEquals(ev->element, "stream:stream"))
{
Log(LOG_ERR, "Excepted stream:stream element.");
@ -163,7 +140,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
stream_id = StrDuplicate(HashMapGet(ev->attrs, "id"));
handshake = ComputeHandshake(shared, stream_id);
Log(LOG_DEBUG, "- sID='%s'", stream_id);
Log(LOG_NOTICE, "- sID='%s'", stream_id);
StreamPrintf(stream, "<handshake>%s</handshake>", handshake);
StreamFlush(stream);
XMLFreeEvent(ev);
@ -177,54 +154,11 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
}
break;
}
if (!ev || ev->type != XML_LEXER_ELEM ||
if (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);
@ -235,7 +169,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
ret = true;
/* We can uhh, send stanzas, and receive them! */
Log(LOG_DEBUG, "Communications to '%s' established.", as);
Log(LOG_INFO, "Communications to '%s' established.", as);
XMLFreeEvent(ev);
Free(stream_id);
@ -265,43 +199,8 @@ XMPPEndCompStream(XMPPComponent *comp)
{
return;
}
pthread_mutex_lock(&comp->write_lock);
StreamClose(comp->stream);
comp->stream = NULL;
pthread_mutex_unlock(&comp->write_lock);
pthread_mutex_destroy(&comp->write_lock);
StreamClose(comp->stream);
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,8 +8,6 @@
#include <Parsee.h>
#include <XML.h>
#include "XMPPThread/internal.h"
bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
@ -20,6 +18,8 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
return false;
}
pthread_mutex_lock(&jabber->write_lock);
iq_query = XMLCreateTag("iq");
query = XMLCreateTag("query");
@ -35,7 +35,8 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
XMLElement *identity;
XMPPSendStanza(jabber, iq_query, 10000);
XMLEncode(jabber->stream, iq_query);
StreamFlush(jabber->stream);
XMLFreeElement(iq_query);
/* Except an IQ reply. 10 seconds of timeout is pretty
@ -44,8 +45,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");
@ -56,11 +57,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
"conference"))
{
XMLFreeElement(iq_query);
Log(LOG_DEBUG, "MUC INFO ERROR");
Log(LOG_DEBUG,
"identityp=%p category=%s", identity,
identity ? HashMapGet(identity->attrs, "category") : NULL
);
pthread_mutex_unlock(&jabber->write_lock);
return false;
}
@ -76,6 +73,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
XMLFreeElement(iq_query);
}
}
pthread_mutex_unlock(&jabber->write_lock);
return true;
}
@ -118,6 +116,7 @@ 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);
@ -161,123 +160,10 @@ XMPPRequestVoice(XMPPComponent *jabber, char *from, char *muc)
}
XMLAddChild(stanza, x);
}
XMPPSendStanza(jabber, stanza, 10000);
XMLEncode(jabber->stream, stanza);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
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);
}

View file

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

View file

@ -7,8 +7,6 @@
#include <Parsee.h>
#include <XML.h>
#include <stdlib.h>
void
XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
@ -68,13 +66,103 @@ XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
}
XMPPSendStanza(comp, message, 10000);
pthread_mutex_lock(&comp->write_lock);
XMLEncode(comp->stream, message);
StreamFlush(comp->stream);
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)
{
@ -83,8 +171,6 @@ 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");
}
@ -161,31 +247,27 @@ XMPPGetReply(XMLElement *elem)
return HashMapGet(rep->attrs, "id");
}
ssize_t
XMPPGetReplyOffset(XMLElement *elem)
void
XMPPAnnotatePresence(XMLElement *presence)
{
if (!elem)
XMLElement *c;
char *ver;
if (!presence)
{
return -1;
return;
}
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;
ver = XMPPGenerateVer();
c = XMLCreateTag("c");
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
XMLAddAttr(c, "hash", "sha-1");
XMLAddAttr(c, "node", REPOSITORY);
XMLAddAttr(c, "ver", ver);
Free(ver);
XMLAddChild(presence, c);
}
char *
XMPPGetModeration(XMLElement *stanza)
{
@ -239,51 +321,35 @@ XMPPHasError(XMLElement *stanza, char *type)
}
XMLElement *
XMPPSendDisco(ParseeData *data, char *from, char *to)
XMPPSendDisco(XMPPComponent *jabber, char *from, char *to)
{
XMPPComponent *jabber = data ? data->jabber : NULL;
XMLElement *ret, *iq;
char *identifier;
if (!data || !jabber || !from || !to)
if (!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(64)));
XMLAddAttr(iq, "id", (identifier = StrRandom(69)));
{
XMLElement *query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "http://jabber.org/protocol/disco#info");
XMLAddChild(iq, query);
}
XMPPSendStanza(jabber, iq, 10000);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
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,17 +34,7 @@ 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)
{
@ -128,7 +118,6 @@ XMPPCreateManager(void *cookie)
ret->commands = HashMapCreate();
ret->sessions = HashMapCreate();
ret->cookie = cookie;
ret->filter = XMPPDefaultFilter;
return ret;
}
@ -172,12 +161,12 @@ XMPPFreeManager(XMPPCommandManager *manager)
Free(manager);
}
void
XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s)
XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p)
{
char *node_name;
XMPPCommand *val;
XMLElement *item;
if (!m || !p || !jid || !s)
if (!m || !p || !jid)
{
return;
}
@ -185,14 +174,11 @@ XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement
pthread_mutex_lock(&m->lock);
while (HashMapIterate(m->commands, &node_name, (void **) &val))
{
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);
}
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);
}
@ -220,16 +206,6 @@ 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)
{
@ -266,7 +242,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
/* This is an execution. */
cmd = HashMapGet(m->commands, node);
if (!cmd || !m->filter(m, node, stanza))
if (!cmd)
{
/* TODO: Set an error note */
goto end;
@ -298,7 +274,10 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
x = XMPPFormifyCommand(m, cmd, from);
XMLAddChild(command_xml, x);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq);
goto end;
@ -326,7 +305,10 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, NULL);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq);
InvalidateSession(m, session_id);
@ -366,7 +348,10 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, x_form);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq);
InvalidateSession(m, session_given);

View file

@ -43,6 +43,7 @@ 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,11 +15,19 @@ 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");
@ -50,7 +58,4 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
DbUnlock(data->db, ref);
}
XMLAddChild(out, x);
(void) form;
(void) from;
}

View file

@ -29,6 +29,4 @@ 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 = NULL;
XMPPOption *admin_opt;
char *trimmed = ParseeTrimJID(from);
if (!ParseeIsAdmin(data, trimmed))
@ -104,17 +104,14 @@ 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

@ -1,49 +0,0 @@
#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;
}

View file

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

@ -1,67 +0,0 @@
#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,6 +87,4 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
}
DbUnlock(data->db, ref);
}
(void) form;
}

View file

@ -14,6 +14,7 @@
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;
@ -25,6 +26,13 @@ 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");
@ -67,7 +75,4 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
EndItem();
}
XMLAddChild(out, x);
(void) form;
(void) m;
}

View file

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

View file

@ -3,7 +3,6 @@
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Log.h>
#include <StringStream.h>
#include <XMPPCommand.h>
@ -13,86 +12,33 @@
#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(IQFeatures *features)
XMPPGenerateVer(void)
{
char *S = NULL;
unsigned char *Sha = NULL;
Array *identities = ArrayCreate();
Array *features = ArrayCreate();
size_t i;
/* Initialise identity table, to be sorted */
ArraySort(features->identity, IdentitySort);
for (i = 0; i < ArraySize(features->identity); i++)
#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++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
XMPPIdentity *identity = ArrayGet(identities, i);
char *id_chunk = StrConcat(7,
identity->category, "/",
identity->type, "/",
@ -104,10 +50,10 @@ XMPPGenerateVer(IQFeatures *features)
Free(id_chunk);
}
ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features->adverts); i++)
ArraySort(features, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features); i++)
{
char *feature = ArrayGet(features->adverts, i);
char *feature = ArrayGet(features, i);
char *tmp = S;
S = StrConcat(3, S, feature, "<");
Free(tmp);
@ -118,64 +64,16 @@ XMPPGenerateVer(IQFeatures *features)
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