forked from Telodendria/Telodendria
Compare commits
No commits in common. "roomwerk" and "master" have entirely different histories.
67 changed files with 136 additions and 9040 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -16,5 +16,3 @@ contrib/.vagrant
|
||||||
src/Schema
|
src/Schema
|
||||||
src/include/Schema
|
src/include/Schema
|
||||||
man/mandoc.db
|
man/mandoc.db
|
||||||
*swp
|
|
||||||
.ccls-cache
|
|
||||||
|
|
|
@ -45,9 +45,6 @@
|
||||||
"state_key": {
|
"state_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"redacts": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
{
|
|
||||||
"guard": "TELODENDRIA_KEY_UPLOAD_H",
|
|
||||||
"header": "Schema/KeyUpload.h",
|
|
||||||
"types": {
|
|
||||||
"DeviceKeys": {
|
|
||||||
"type": "struct",
|
|
||||||
"fields": {
|
|
||||||
"device_id": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"algorithms": {
|
|
||||||
"type": "[string]",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"keys": {
|
|
||||||
"type": "{string}",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"signatures": {
|
|
||||||
"//": "TODO: More complex j2s types.",
|
|
||||||
"//": "This is meant to be a map from user ID to ",
|
|
||||||
"//": "algo+device ID to a signature(string).",
|
|
||||||
"type": "object",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"KeyResponse": {
|
|
||||||
"type": "struct",
|
|
||||||
"fields": {
|
|
||||||
"one_time_key_counts": {
|
|
||||||
"type": "{integer}",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"KeyUploadRequest": {
|
|
||||||
"type": "struct",
|
|
||||||
"fields": {
|
|
||||||
"device_keys": {
|
|
||||||
"type": "DeviceKeys",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"fallback_keys": {
|
|
||||||
"//": "This is a one-time key.",
|
|
||||||
"type": "object",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"one_time_keys": {
|
|
||||||
"//": "This is a one-time key.",
|
|
||||||
"type": "object",
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,16 +2,6 @@
|
||||||
"guard": "TELODENDRIA_SCHEMA_PDUV1_H",
|
"guard": "TELODENDRIA_SCHEMA_PDUV1_H",
|
||||||
"header": "Schema/PduV1.h",
|
"header": "Schema/PduV1.h",
|
||||||
"types": {
|
"types": {
|
||||||
"PduV1Status": {
|
|
||||||
"type": "enum",
|
|
||||||
"fields": {
|
|
||||||
"dropped": { "name": "PDUV1_STATUS_DROPPED" },
|
|
||||||
"softfail": { "name": "PDUV1_STATUS_SOFTFAIL" },
|
|
||||||
|
|
||||||
"accepted": { "name": "PDUV1_STATUS_ACCEPTED" },
|
|
||||||
"rejected": { "name": "PDUV1_STATUS_REJECTED" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"PduV1EventHash": {
|
"PduV1EventHash": {
|
||||||
"type": "struct",
|
"type": "struct",
|
||||||
"fields": {
|
"fields": {
|
||||||
|
@ -26,22 +16,6 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"age": {
|
"age": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
|
||||||
"next_events": {
|
|
||||||
"type": "[string]",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"pdu_status": {
|
|
||||||
"type": "PduV1Status",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"redacted_because": {
|
|
||||||
"type": "object",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"transaction_id": {
|
|
||||||
"type": "string",
|
|
||||||
"required": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -77,8 +51,7 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"redacts": {
|
"redacts": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"required": false
|
|
||||||
},
|
},
|
||||||
"room_id": {
|
"room_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"guard": "TELODENDRIA_SCHEMA_RELATION_H",
|
|
||||||
"header": "Schema/Relation.h",
|
|
||||||
"types": {
|
|
||||||
"Relation": {
|
|
||||||
"type": "struct",
|
|
||||||
"fields": {
|
|
||||||
"event_id": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"rel_type": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -48,9 +48,6 @@
|
||||||
"room_alias_name": {
|
"room_alias_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"room_id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"preset": {
|
"preset": {
|
||||||
"type": "RoomCreatePreset"
|
"type": "RoomCreatePreset"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
{
|
|
||||||
"guard": "TELODENDRIA_SCHEMA_SYNCRESPONSE_H",
|
|
||||||
"header": "Schema\/SyncResponse.h",
|
|
||||||
"types": {
|
|
||||||
"Event": {
|
|
||||||
"fields": {
|
|
||||||
"content": {
|
|
||||||
"type": "object",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"AccountData": {
|
|
||||||
"fields": {
|
|
||||||
"events": { "type": "[Event]" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"InviteState": {
|
|
||||||
"fields": {
|
|
||||||
"events": { "type": "[StrippedStateEvent]" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"States": {
|
|
||||||
"fields": {
|
|
||||||
"events": { "type": "array" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"StrippedStateEvent": {
|
|
||||||
"fields": {
|
|
||||||
"content": {
|
|
||||||
"type": "object",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"sender": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"state_key": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"InvitedRooms": {
|
|
||||||
"fields": {
|
|
||||||
"invite_state": { "type": "InviteState" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"ClientEventWithoutRoomID": {
|
|
||||||
"fields": {
|
|
||||||
"content": { "type": "object", "required": true },
|
|
||||||
"event_id": { "type": "string", "required": true },
|
|
||||||
"origin_server_ts": { "type": "integer", "required": true },
|
|
||||||
"sender": { "type": "string", "required": true },
|
|
||||||
"state_key": { "type": "string" },
|
|
||||||
"redacts": { "type": "string" },
|
|
||||||
"_unsigned": { "type": "object" },
|
|
||||||
"type": { "type": "string", "required": true }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"Timeline": {
|
|
||||||
"fields": {
|
|
||||||
"events": { "type": "[ClientEventWithoutRoomID]", "required": true },
|
|
||||||
"limited": { "type": "boolean" },
|
|
||||||
"prev_batch": { "type": "string" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"JoinedRooms": {
|
|
||||||
"fields": {
|
|
||||||
"timeline": { "type": "Timeline" },
|
|
||||||
"state": { "type": "States" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"Rooms": {
|
|
||||||
"fields": {
|
|
||||||
"invite": { "type": "{InvitedRooms}" },
|
|
||||||
"join": { "type": "{JoinedRooms}" }
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
},
|
|
||||||
"SyncResponse": {
|
|
||||||
"fields": {
|
|
||||||
"next_batch": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"account_data": {
|
|
||||||
"type": "AccountData"
|
|
||||||
},
|
|
||||||
"rooms": {
|
|
||||||
"type": "Rooms"
|
|
||||||
},
|
|
||||||
"device_one_time_keys_count": {
|
|
||||||
"type": "{integer}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "struct"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
4
configure
vendored
4
configure
vendored
|
@ -15,7 +15,7 @@ TOOLS="tools/src"
|
||||||
SCHEMA="Schema"
|
SCHEMA="Schema"
|
||||||
CYTOPLASM="Cytoplasm"
|
CYTOPLASM="Cytoplasm"
|
||||||
|
|
||||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${SRC} -I${BUILD}"
|
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||||
LIBS="-lm -pthread -lCytoplasm"
|
LIBS="-lm -pthread -lCytoplasm"
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ print_obj() {
|
||||||
get_deps() {
|
get_deps() {
|
||||||
src="$1"
|
src="$1"
|
||||||
|
|
||||||
${CC} -I${SRC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
${CC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
||||||
| grep '^#' \
|
| grep '^#' \
|
||||||
| awk '{print $3}' \
|
| awk '{print $3}' \
|
||||||
| cut -d '"' -f 2 \
|
| cut -d '"' -f 2 \
|
||||||
|
|
6
contrib/Makefile
Normal file
6
contrib/Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
all:
|
||||||
|
sh tools/bin/td
|
||||||
|
|
||||||
|
install:
|
||||||
|
install build/telodendria $(PREFIX)/bin/telodendria
|
||||||
|
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;
|
|
@ -14,8 +14,6 @@ administrator API; the regular Matrix API is unaffected.
|
||||||
registration tokens.
|
registration tokens.
|
||||||
- **CONFIG:** Allows a user to modify the Telodendria server daemon's
|
- **CONFIG:** Allows a user to modify the Telodendria server daemon's
|
||||||
configuration.
|
configuration.
|
||||||
- **APPSERVICE:** Allows a user to register and manage application
|
|
||||||
services.
|
|
||||||
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
||||||
privileges or the privileges of other local users.
|
privileges or the privileges of other local users.
|
||||||
- **ALIAS:** Allows a user to modify and see room aliases created by
|
- **ALIAS:** Allows a user to modify and see room aliases created by
|
||||||
|
|
|
@ -22,16 +22,11 @@
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "Cytoplasm/Io.h"
|
|
||||||
#include "Cytoplasm/Stream.h"
|
|
||||||
#include <CanonicalJson.h>
|
#include <CanonicalJson.h>
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Array.h>
|
#include <Cytoplasm/Array.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
#include <Cytoplasm/Sha.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -164,55 +159,3 @@ CanonicalJsonEncode(HashMap * object, Stream * out)
|
||||||
ArrayFree(keys);
|
ArrayFree(keys);
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
|
||||||
StringIoRead(void *cookie, void *buf, size_t count)
|
|
||||||
{
|
|
||||||
(void) cookie;
|
|
||||||
(void) buf;
|
|
||||||
(void) count;
|
|
||||||
return -1; /* TODO: Consider reading properly */
|
|
||||||
}
|
|
||||||
static ssize_t
|
|
||||||
StringIoWrite(void *c, void *buf, size_t count)
|
|
||||||
{
|
|
||||||
if (count > 0)
|
|
||||||
{
|
|
||||||
char **str = c;
|
|
||||||
char *stringised = Malloc(count + 1);
|
|
||||||
char *new;
|
|
||||||
memcpy(stringised, buf, count);
|
|
||||||
new = StrConcat(2, *str, stringised);
|
|
||||||
|
|
||||||
Free(stringised);
|
|
||||||
if (*str)
|
|
||||||
{
|
|
||||||
Free(*str);
|
|
||||||
}
|
|
||||||
*str = new;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
static const IoFunctions Functions = {
|
|
||||||
.read = StringIoRead,
|
|
||||||
.write = StringIoWrite,
|
|
||||||
.close = NULL,
|
|
||||||
.seek = NULL
|
|
||||||
};
|
|
||||||
unsigned char *
|
|
||||||
CanonicalJsonHash(HashMap *json)
|
|
||||||
{
|
|
||||||
char *string = NULL;
|
|
||||||
unsigned char *sha;
|
|
||||||
Io *string_writer = IoCreate(&string, Functions);
|
|
||||||
Stream *io_stream = StreamIo(string_writer);
|
|
||||||
CanonicalJsonEncode(json, io_stream);
|
|
||||||
StreamFlush(io_stream);
|
|
||||||
|
|
||||||
sha = Sha256(string);
|
|
||||||
|
|
||||||
Free(string);
|
|
||||||
StreamClose(io_stream);
|
|
||||||
|
|
||||||
return sha;
|
|
||||||
}
|
|
||||||
|
|
21
src/Config.c
21
src/Config.c
|
@ -230,24 +230,3 @@ ConfigLogLevelToSyslog(ConfigLogLevel level)
|
||||||
}
|
}
|
||||||
return LOG_INFO;
|
return LOG_INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
char *
|
|
||||||
ConfigGetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
250
src/Filter.c
250
src/Filter.c
|
@ -24,256 +24,12 @@
|
||||||
*/
|
*/
|
||||||
#include <Filter.h>
|
#include <Filter.h>
|
||||||
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
#include <Matrix.h>
|
|
||||||
|
|
||||||
/* Verifies whenever an item passes through a set of blacklisted and
|
|
||||||
* whitelisted groups(e.g "not_rooms"/"rooms"), and makes the calling
|
|
||||||
* function return true/false if it is explicitely in a filtered list,
|
|
||||||
* or does nothing otherwise, to allow for chaining multiple filters. */
|
|
||||||
#define DAFiltered(blacklist, whitelist, item) \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
size_t i, count; \
|
|
||||||
count = ArraySize(blacklist); \
|
|
||||||
for (i = 0; i < count; i++) \
|
|
||||||
{ \
|
|
||||||
char *notItem = ArrayGet(blacklist, i); \
|
|
||||||
if (StrEquals(item, notItem)) \
|
|
||||||
{ \
|
|
||||||
return true; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
count = ArraySize(whitelist); \
|
|
||||||
if (count) \
|
|
||||||
{ \
|
|
||||||
for (i = 0; i < count; i++) \
|
|
||||||
{ \
|
|
||||||
char *yesItem = ArrayGet(whitelist, i); \
|
|
||||||
if (StrEquals(yesItem, item)) \
|
|
||||||
{ \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
return true; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static bool
|
|
||||||
IsSenderFiltered(Filter *filter, char *senderID)
|
|
||||||
{
|
|
||||||
if (!filter || !senderID)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.account_data.not_senders,
|
|
||||||
filter->room.account_data.senders,
|
|
||||||
senderID
|
|
||||||
);
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.state.not_senders,
|
|
||||||
filter->room.state.senders,
|
|
||||||
senderID
|
|
||||||
);
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.ephemeral.not_senders,
|
|
||||||
filter->room.ephemeral.senders,
|
|
||||||
senderID
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
IsRoomFiltered(Filter *filter, char *roomID)
|
|
||||||
{
|
|
||||||
if (!filter || !roomID)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.not_rooms,
|
|
||||||
filter->room.rooms,
|
|
||||||
roomID
|
|
||||||
);
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.account_data.not_rooms,
|
|
||||||
filter->room.account_data.rooms,
|
|
||||||
roomID
|
|
||||||
);
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.state.not_rooms,
|
|
||||||
filter->room.state.rooms,
|
|
||||||
roomID
|
|
||||||
);
|
|
||||||
DAFiltered(
|
|
||||||
filter->room.ephemeral.not_rooms,
|
|
||||||
filter->room.ephemeral.rooms,
|
|
||||||
roomID
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static HashMap *
|
|
||||||
IncludeFields(Array * fields, HashMap * event)
|
|
||||||
{
|
|
||||||
HashMap *cpy;
|
|
||||||
size_t i, len;
|
|
||||||
if (!event)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fields)
|
|
||||||
{
|
|
||||||
return JsonDuplicate(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpy = HashMapCreate();
|
|
||||||
|
|
||||||
/* NOTE: We intentionally add some fields due to some requirements
|
|
||||||
* around certain schemas. The specification does find such behavior
|
|
||||||
* compliant:
|
|
||||||
* > "[...] A server may include more fields than were requested." */
|
|
||||||
#define CopyField(field) \
|
|
||||||
HashMapSet(cpy, field, JsonValueDuplicate(HashMapGet(event, field)))
|
|
||||||
|
|
||||||
CopyField("event_id");
|
|
||||||
CopyField("origin_server_ts");
|
|
||||||
CopyField("sender");
|
|
||||||
CopyField("type");
|
|
||||||
len = ArraySize(fields);
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
char *field = ArrayGet(fields, i);
|
|
||||||
JsonValue *val = JsonValueDuplicate(MatrixGetJSON(event, field));
|
|
||||||
|
|
||||||
JsonValueFree(MatrixSetJSON(cpy, field, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Only copy it if not already present. */
|
|
||||||
if (!HashMapGet(cpy, "content"))
|
|
||||||
{
|
|
||||||
CopyField("content");
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CopyField
|
|
||||||
return cpy;
|
|
||||||
}
|
|
||||||
/* TODO: MORE FILTERS! */
|
|
||||||
HashMap *
|
HashMap *
|
||||||
FilterApply(Filter * filter, HashMap * event)
|
FilterApply(Filter * filter, HashMap * event)
|
||||||
{
|
{
|
||||||
HashMap *copy;
|
(void) filter;
|
||||||
char *sender, *room;
|
(void) event;
|
||||||
if (!event)
|
return NULL;
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (!filter)
|
|
||||||
{
|
|
||||||
/* Do NOT filter anything out */
|
|
||||||
return JsonDuplicate(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
sender = JsonValueAsString(HashMapGet(event, "sender"));
|
|
||||||
room = JsonValueAsString(HashMapGet(event, "room_id"));
|
|
||||||
|
|
||||||
if (IsRoomFiltered(filter, room) ||
|
|
||||||
IsSenderFiltered(filter, sender))
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
copy = IncludeFields(filter->event_fields, event);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Filter *
|
|
||||||
FilterDecode(Db *db, char *user, char *filterStr, char **errp)
|
|
||||||
{
|
|
||||||
Filter *ret;
|
|
||||||
DbRef *filterRef;
|
|
||||||
HashMap *filterObj;
|
|
||||||
|
|
||||||
FILE *fakeFile;
|
|
||||||
Stream *fakeStream;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
if (!db || !user || !filterStr)
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp =
|
|
||||||
"Internal error: Field required for FilterDecode is missing.";
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*filterStr != '{')
|
|
||||||
{
|
|
||||||
filterRef = DbLock(db, 3, "filters", user, filterStr);
|
|
||||||
filterObj = DbJson(filterRef);
|
|
||||||
ret = Malloc(sizeof(*ret));
|
|
||||||
memset(ret, 0, sizeof(*ret));
|
|
||||||
|
|
||||||
if (!FilterFromJson(filterObj, ret, &err))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = err;
|
|
||||||
}
|
|
||||||
DbUnlock(db, filterRef);
|
|
||||||
Free(ret);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DbUnlock(db, filterRef);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: This should be it's own JSON function, in my
|
|
||||||
* opinion. */
|
|
||||||
fakeFile = fmemopen(filterStr, strlen(filterStr), "r");
|
|
||||||
fakeStream = StreamFile(fakeFile);
|
|
||||||
filterObj = JsonDecode(fakeStream);
|
|
||||||
StreamClose(fakeStream); /* auto-frees fakeFile */
|
|
||||||
ret = Malloc(sizeof(*ret));
|
|
||||||
memset(ret, 0, sizeof(*ret));
|
|
||||||
|
|
||||||
if (!FilterFromJson(filterObj, ret, &err))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = err;
|
|
||||||
}
|
|
||||||
JsonFree(filterObj);
|
|
||||||
Free(ret);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFree(filterObj);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
FilterDestroy(Filter *filter)
|
|
||||||
{
|
|
||||||
if (!filter)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterFree(filter);
|
|
||||||
Free(filter);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ HtmlBegin(Stream * stream, char *title)
|
||||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||||
"<title>%s | Telodendria</title>"
|
"<title>%s | Telodendria</title>"
|
||||||
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
|
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
|
||||||
"<script src=\"/_matrix/static/telodendria.js\"></script>"
|
"<script src=\"/_matrix/static/telodendria.js\"></script>"
|
||||||
"</head>"
|
"</head>"
|
||||||
"<body>"
|
"<body>"
|
||||||
"<pre class=\"logo\">"
|
"<pre class=\"logo\">"
|
||||||
|
|
44
src/Main.c
44
src/Main.c
|
@ -56,7 +56,6 @@
|
||||||
|
|
||||||
static Array *httpServers;
|
static Array *httpServers;
|
||||||
static volatile int restart;
|
static volatile int restart;
|
||||||
static MatrixHttpHandlerArgs matrixArgs;
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
SignalHandler(int signal)
|
SignalHandler(int signal)
|
||||||
|
@ -80,36 +79,13 @@ SignalHandler(int signal)
|
||||||
for (i = 0; i < ArraySize(httpServers); i++)
|
for (i = 0; i < ArraySize(httpServers); i++)
|
||||||
{
|
{
|
||||||
HttpServer *server = ArrayGet(httpServers, i);
|
HttpServer *server = ArrayGet(httpServers, i);
|
||||||
/* TODO: Notify all users, so that the server doesn't have to
|
|
||||||
* await for a sync reply. */
|
|
||||||
|
|
||||||
UserNotifyAll(matrixArgs.db);
|
|
||||||
HttpServerStop(server);
|
HttpServerStop(server);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Move this! */
|
|
||||||
static void
|
|
||||||
UsersClean(MatrixHttpHandlerArgs *args)
|
|
||||||
{
|
|
||||||
Db *db = args->db;
|
|
||||||
Array *arr = DbList(db, 1, "users");
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(arr); i++)
|
|
||||||
{
|
|
||||||
char *id = ArrayGet(arr, i);
|
|
||||||
User *user = UserLock(db, id);
|
|
||||||
|
|
||||||
UserCleanTemporaryData(user);
|
|
||||||
UserUnlock(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
DbListFree(arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum ArgFlag
|
typedef enum ArgFlag
|
||||||
{
|
{
|
||||||
ARG_VERSION = (1 << 0),
|
ARG_VERSION = (1 << 0),
|
||||||
|
@ -145,6 +121,7 @@ Main(Array * args)
|
||||||
/* Signal handling */
|
/* Signal handling */
|
||||||
struct sigaction sigAction;
|
struct sigaction sigAction;
|
||||||
|
|
||||||
|
MatrixHttpHandlerArgs matrixArgs;
|
||||||
Cron *cron;
|
Cron *cron;
|
||||||
|
|
||||||
char startDir[PATH_MAX];
|
char startDir[PATH_MAX];
|
||||||
|
@ -166,12 +143,10 @@ start:
|
||||||
userInfo = NULL;
|
userInfo = NULL;
|
||||||
groupInfo = NULL;
|
groupInfo = NULL;
|
||||||
cron = NULL;
|
cron = NULL;
|
||||||
tConfig.ok = false;
|
|
||||||
|
|
||||||
token = NULL;
|
token = NULL;
|
||||||
|
|
||||||
memset(&matrixArgs, 0, sizeof(matrixArgs));
|
memset(&matrixArgs, 0, sizeof(matrixArgs));
|
||||||
UserInitialisePushTable();
|
|
||||||
|
|
||||||
if (!LogConfigGlobal())
|
if (!LogConfigGlobal())
|
||||||
{
|
{
|
||||||
|
@ -300,8 +275,7 @@ start:
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tConfig.log.timestampFormat ||
|
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
|
||||||
!StrEquals(tConfig.log.timestampFormat, "default"))
|
|
||||||
{
|
{
|
||||||
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
|
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
|
||||||
}
|
}
|
||||||
|
@ -539,15 +513,10 @@ start:
|
||||||
Log(LOG_DEBUG, "Registering jobs...");
|
Log(LOG_DEBUG, "Registering jobs...");
|
||||||
|
|
||||||
CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UiaCleanup, &matrixArgs);
|
CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UiaCleanup, &matrixArgs);
|
||||||
CronEvery(cron, 5 * 60 * 1000, (JobFunc *) UsersClean, &matrixArgs);
|
|
||||||
|
|
||||||
Log(LOG_NOTICE, "Starting job scheduler...");
|
Log(LOG_NOTICE, "Starting job scheduler...");
|
||||||
CronStart(cron);
|
CronStart(cron);
|
||||||
|
|
||||||
/* We still call it anyways to be sure. */
|
|
||||||
Log(LOG_NOTICE, "Cleaning up user data...");
|
|
||||||
UsersClean(&matrixArgs);
|
|
||||||
|
|
||||||
Log(LOG_NOTICE, "Building routing tree...");
|
Log(LOG_NOTICE, "Building routing tree...");
|
||||||
matrixArgs.router = RouterBuild();
|
matrixArgs.router = RouterBuild();
|
||||||
if (!matrixArgs.router)
|
if (!matrixArgs.router)
|
||||||
|
@ -614,7 +583,6 @@ start:
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
Log(LOG_NOTICE, "Shutting down...");
|
Log(LOG_NOTICE, "Shutting down...");
|
||||||
UserDestroyPushTable();
|
|
||||||
if (httpServers)
|
if (httpServers)
|
||||||
{
|
{
|
||||||
for (i = 0; i < ArraySize(httpServers); i++)
|
for (i = 0; i < ArraySize(httpServers); i++)
|
||||||
|
@ -638,11 +606,9 @@ finish:
|
||||||
CronFree(cron);
|
CronFree(cron);
|
||||||
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
|
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
|
||||||
}
|
}
|
||||||
if (tConfig.ok)
|
|
||||||
{
|
ConfigUnlock(&tConfig);
|
||||||
ConfigUnlock(&tConfig);
|
Log(LOG_DEBUG, "Unlocked configuration.");
|
||||||
Log(LOG_DEBUG, "Unlocked configuration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
DbClose(matrixArgs.db);
|
DbClose(matrixArgs.db);
|
||||||
Log(LOG_DEBUG, "Closed database.");
|
Log(LOG_DEBUG, "Closed database.");
|
||||||
|
|
178
src/Matrix.c
178
src/Matrix.c
|
@ -360,181 +360,3 @@ MatrixClientWellKnown(char *base, char *identity)
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonValue *
|
|
||||||
MatrixGetJSON(HashMap *json, char *string)
|
|
||||||
{
|
|
||||||
JsonValue *retVal;
|
|
||||||
HashMap *currentObj;
|
|
||||||
char *field, *fieldTemp;
|
|
||||||
size_t i, length;
|
|
||||||
|
|
||||||
if (!json || !string)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentObj = json;
|
|
||||||
retVal = NULL;
|
|
||||||
length = strlen(string);
|
|
||||||
field = NULL;
|
|
||||||
|
|
||||||
#define Append(ch) do \
|
|
||||||
{ \
|
|
||||||
char chb[2] = { ch, 0 }; \
|
|
||||||
fieldTemp = field; \
|
|
||||||
field = StrConcat(2, field, chb); \
|
|
||||||
Free(fieldTemp); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* We include the 0-terminator as a valid separator. */
|
|
||||||
for (i = 0; i < length + 1; i++)
|
|
||||||
{
|
|
||||||
char currentChar = string[i];
|
|
||||||
char escape;
|
|
||||||
if (currentChar == '\\' && (escape = string[i+1]))
|
|
||||||
{
|
|
||||||
if (escape != '.' && escape != '\\')
|
|
||||||
{
|
|
||||||
Append('\\');
|
|
||||||
}
|
|
||||||
Append(escape);
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (currentChar == '.' || currentChar == '\0')
|
|
||||||
{
|
|
||||||
if (!field || !currentObj)
|
|
||||||
{
|
|
||||||
Free(field);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
retVal = HashMapGet(currentObj, field);
|
|
||||||
currentObj = JsonValueAsObject(retVal);
|
|
||||||
|
|
||||||
Free(field);
|
|
||||||
field = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Append(currentChar);
|
|
||||||
}
|
|
||||||
#undef Append
|
|
||||||
|
|
||||||
if (field)
|
|
||||||
{
|
|
||||||
/* This is weird. */
|
|
||||||
Free(field);
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValue *
|
|
||||||
MatrixSetJSON(HashMap *json, char *string, JsonValue *new)
|
|
||||||
{
|
|
||||||
JsonValue *retVal;
|
|
||||||
HashMap *currentObj;
|
|
||||||
char *field, *fieldTemp;
|
|
||||||
size_t i, length;
|
|
||||||
|
|
||||||
if (!json || !string || !new)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentObj = json;
|
|
||||||
retVal = NULL;
|
|
||||||
length = strlen(string);
|
|
||||||
field = NULL;
|
|
||||||
|
|
||||||
#define Append(ch) do \
|
|
||||||
{ \
|
|
||||||
char chb[2] = { ch, 0 }; \
|
|
||||||
fieldTemp = field; \
|
|
||||||
field = StrConcat(2, field, chb); \
|
|
||||||
Free(fieldTemp); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* We include the 0-terminator as a valid separator. */
|
|
||||||
for (i = 0; i < length + 1; i++)
|
|
||||||
{
|
|
||||||
char currentChar = string[i];
|
|
||||||
char escape;
|
|
||||||
if (currentChar == '\\' && (escape = string[i+1]))
|
|
||||||
{
|
|
||||||
if (escape != '.' && escape != '\\')
|
|
||||||
{
|
|
||||||
Append('\\');
|
|
||||||
}
|
|
||||||
Append(escape);
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (currentChar == '.' || currentChar == '\0')
|
|
||||||
{
|
|
||||||
if (!field || !currentObj)
|
|
||||||
{
|
|
||||||
Free(field);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentChar == '.')
|
|
||||||
{
|
|
||||||
retVal = HashMapGet(currentObj, field);
|
|
||||||
if (!retVal)
|
|
||||||
{
|
|
||||||
retVal = JsonValueObject(HashMapCreate());
|
|
||||||
HashMapSet(currentObj, field, retVal);
|
|
||||||
}
|
|
||||||
currentObj = JsonValueAsObject(retVal);
|
|
||||||
|
|
||||||
Free(field);
|
|
||||||
field = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
retVal = HashMapSet(currentObj, field, new);
|
|
||||||
Free(field);
|
|
||||||
field = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Append(currentChar);
|
|
||||||
}
|
|
||||||
#undef Append
|
|
||||||
|
|
||||||
if (field)
|
|
||||||
{
|
|
||||||
/* This is weird. */
|
|
||||||
Free(field);
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
MatrixCheckFloats(HashMap *obj)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
char *key;
|
|
||||||
JsonValue *value;
|
|
||||||
if (!obj)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while (HashMapIterateReentrant(obj, &key, (void **) &value, &i))
|
|
||||||
{
|
|
||||||
if (JsonValueType(value) == JSON_FLOAT)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (JsonValueType(value) == JSON_OBJECT)
|
|
||||||
{
|
|
||||||
if (!MatrixCheckFloats(JsonValueAsObject(value)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -487,7 +487,7 @@ ParserRecomposeCommonID(CommonID id)
|
||||||
if (id.server.hostname)
|
if (id.server.hostname)
|
||||||
{
|
{
|
||||||
char *server = ParserRecomposeServerPart(id.server);
|
char *server = ParserRecomposeServerPart(id.server);
|
||||||
char *tmp = StrConcat(3, ret, ":", server);
|
char *tmp = StrConcat(4, "@", ret, ":", server);
|
||||||
Free(ret);
|
Free(ret);
|
||||||
Free(server);
|
Free(server);
|
||||||
|
|
||||||
|
|
804
src/Room.c
804
src/Room.c
|
@ -23,97 +23,35 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Array.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
#include <Room.h>
|
||||||
|
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Cytoplasm/Base64.h>
|
|
||||||
#include <Cytoplasm/Util.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
|
|
||||||
#include <Schema/RoomCreateRequest.h>
|
#include <Schema/RoomCreateRequest.h>
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
#include <Schema/PduV3.h>
|
|
||||||
|
|
||||||
#include <CanonicalJson.h>
|
struct Room
|
||||||
#include <Config.h>
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <Matrix.h>
|
|
||||||
#include <State.h>
|
|
||||||
#include <Event.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
static char *
|
|
||||||
GenerateRoomId(RoomCreateRequest *req, ServerPart s)
|
|
||||||
{
|
{
|
||||||
CommonID cid;
|
Db *db;
|
||||||
char *string;
|
DbRef *ref;
|
||||||
|
|
||||||
cid.sigil = '!';
|
char *id;
|
||||||
cid.local = (req && req->room_id) ?
|
int version;
|
||||||
StrDuplicate(req->room_id) :
|
};
|
||||||
StrRandom(32);
|
|
||||||
cid.server = s;
|
|
||||||
string = ParserRecomposeCommonID(cid);
|
|
||||||
Free(cid.local);
|
|
||||||
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
Room *
|
Room *
|
||||||
RoomCreate(Db * db, User *user, RoomCreateRequest * req, ServerPart s)
|
RoomCreate(Db * db, RoomCreateRequest * req)
|
||||||
{
|
{
|
||||||
Room *room;
|
(void) db;
|
||||||
char *version_string, *full_creator;
|
(void) req;
|
||||||
int version_num = 1;
|
return NULL;
|
||||||
HashMap *json;
|
|
||||||
if (!db || !req || !user)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
version_string = req->room_version;
|
|
||||||
if (version_string)
|
|
||||||
{
|
|
||||||
/* TODO: Eventually use something else than room version 1 by
|
|
||||||
* default, and maybe add a config parameter. */
|
|
||||||
version_num = atoi(version_string);
|
|
||||||
version_num = version_num == 0 ? 1 : version_num;
|
|
||||||
}
|
|
||||||
room = Malloc(sizeof(Room));
|
|
||||||
room->db = db;
|
|
||||||
room->creator.hostname = s.hostname ? StrDuplicate(s.hostname) : NULL;
|
|
||||||
room->creator.port = s.port ? StrDuplicate(s.port) : NULL;
|
|
||||||
room->id = GenerateRoomId(req, s);
|
|
||||||
room->version = version_num;
|
|
||||||
|
|
||||||
room->state_ref = DbCreate(db, 3, "rooms", room->id, "state");
|
|
||||||
room->leaves_ref = DbCreate(db, 3, "rooms", room->id, "leaves");
|
|
||||||
json = DbJson(room->leaves_ref);
|
|
||||||
JsonSet(json, JsonValueArray(ArrayCreate()), 1, "leaves");
|
|
||||||
full_creator = ParserRecomposeServerPart(room->creator);
|
|
||||||
JsonSet(json, JsonValueString(full_creator), 1, "creator");
|
|
||||||
JsonSet(json, JsonValueInteger(version_num), 1, "version");
|
|
||||||
Free(full_creator);
|
|
||||||
|
|
||||||
RoomPopulate(room, user, req, s);
|
|
||||||
|
|
||||||
return room;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room *
|
Room *
|
||||||
RoomLock(Db * db, char *id)
|
RoomLock(Db * db, char *id)
|
||||||
{
|
{
|
||||||
DbRef *state_ref, *leaves_ref;
|
DbRef *ref;
|
||||||
HashMap *json;
|
|
||||||
Room *room;
|
Room *room;
|
||||||
|
|
||||||
if (!db || !id)
|
if (!db || !id)
|
||||||
|
@ -121,10 +59,9 @@ RoomLock(Db * db, char *id)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
state_ref = DbLock(db, 3, "rooms", id, "state");
|
ref = DbLock(db, 3, "rooms", id, "state");
|
||||||
leaves_ref = DbLock(db, 3, "rooms", id, "leaves");
|
|
||||||
|
|
||||||
if (!state_ref || !leaves_ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -132,21 +69,13 @@ RoomLock(Db * db, char *id)
|
||||||
room = Malloc(sizeof(Room));
|
room = Malloc(sizeof(Room));
|
||||||
if (!room)
|
if (!room)
|
||||||
{
|
{
|
||||||
DbUnlock(db, state_ref);
|
DbUnlock(db, ref);
|
||||||
DbUnlock(db, leaves_ref);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
room->db = db;
|
room->db = db;
|
||||||
room->state_ref = state_ref;
|
room->ref = ref;
|
||||||
room->leaves_ref = leaves_ref;
|
|
||||||
room->id = StrDuplicate(id);
|
room->id = StrDuplicate(id);
|
||||||
room->version = JsonValueAsInteger(HashMapGet(DbJson(leaves_ref), "version"));
|
|
||||||
|
|
||||||
json = DbJson(room->leaves_ref);
|
|
||||||
ParseServerPart(
|
|
||||||
JsonValueAsString(JsonGet(json, 1, "creator")),
|
|
||||||
&room->creator);
|
|
||||||
|
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
@ -155,8 +84,7 @@ int
|
||||||
RoomUnlock(Room * room)
|
RoomUnlock(Room * room)
|
||||||
{
|
{
|
||||||
Db *db;
|
Db *db;
|
||||||
DbRef *state_ref;
|
DbRef *ref;
|
||||||
DbRef *leaves_ref;
|
|
||||||
|
|
||||||
if (!room)
|
if (!room)
|
||||||
{
|
{
|
||||||
|
@ -164,717 +92,33 @@ RoomUnlock(Room * room)
|
||||||
}
|
}
|
||||||
|
|
||||||
db = room->db;
|
db = room->db;
|
||||||
state_ref = room->state_ref;
|
ref = room->ref;
|
||||||
leaves_ref = room->leaves_ref;
|
|
||||||
|
|
||||||
Free(room->id);
|
Free(room->id);
|
||||||
Free(room);
|
Free(room);
|
||||||
|
|
||||||
ServerPartFree(room->creator);
|
return DbUnlock(db, ref);
|
||||||
|
|
||||||
return DbUnlock(db, state_ref) &&
|
|
||||||
DbUnlock(db, leaves_ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t
|
|
||||||
ParsePL(JsonValue *v, int64_t def)
|
|
||||||
{
|
|
||||||
if (!v)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
if (JsonValueType(v) == JSON_INTEGER)
|
|
||||||
{
|
|
||||||
return JsonValueAsInteger(v);
|
|
||||||
}
|
|
||||||
if (JsonValueType(v) == JSON_STRING)
|
|
||||||
{
|
|
||||||
char *string = JsonValueAsString(v), *end;
|
|
||||||
int64_t value= strtoll(string, &end, 10);
|
|
||||||
if (!((*string != '\0') && (*end == '\0')))
|
|
||||||
{
|
|
||||||
/* Invalid string: return the default. */
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return def;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
EventContentHash(HashMap * pdu_json)
|
RoomIdGet(Room * room)
|
||||||
{
|
{
|
||||||
HashMap * copy = JsonDuplicate(pdu_json);
|
return room ? room->id : NULL;
|
||||||
unsigned char *sha;
|
|
||||||
char *b64;
|
|
||||||
|
|
||||||
JsonValueFree(HashMapDelete(copy, "unsigned"));
|
|
||||||
JsonValueFree(HashMapDelete(copy, "signatures"));
|
|
||||||
JsonValueFree(HashMapDelete(copy, "hashes"));
|
|
||||||
|
|
||||||
sha = CanonicalJsonHash(copy);
|
|
||||||
b64 = Base64Encode((const char *) sha, 32);
|
|
||||||
Base64Unpad(b64, strlen(b64));
|
|
||||||
|
|
||||||
Free(sha);
|
|
||||||
JsonFree(copy);
|
|
||||||
|
|
||||||
return b64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Separate this when we eventually steamroll */
|
int
|
||||||
static HashMap *
|
RoomVersionGet(Room * room)
|
||||||
RoomEventSendV3(Room * room, HashMap * event)
|
|
||||||
{
|
{
|
||||||
/* TODO */
|
return room ? room->version : 0;
|
||||||
(void) room;
|
|
||||||
(void) event;
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap *
|
HashMap *
|
||||||
RoomEventSend(Room * room, HashMap * event, char **errp)
|
RoomStateGet(Room * room)
|
||||||
{
|
|
||||||
if (!room || !event)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (!MatrixCheckFloats(event))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Event contains forbidden float value";
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (room->version < 3)
|
|
||||||
{
|
|
||||||
/* Manage with PDUv1 */
|
|
||||||
return RoomEventSendV1(room, event, errp);
|
|
||||||
}
|
|
||||||
/* Manage with PDUv3 otherwise */
|
|
||||||
return RoomEventSendV3(room, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
CreateSafeID(char *unsafe_id)
|
|
||||||
{
|
|
||||||
size_t length = strlen(unsafe_id);
|
|
||||||
char *safe_id = Malloc(length + 1);
|
|
||||||
size_t i;
|
|
||||||
/* Creates a room ID safe to be put into the database
|
|
||||||
* for room version 3 and above.
|
|
||||||
* (with '/'s replaced by '-') */
|
|
||||||
memcpy(safe_id, unsafe_id, length + 1);
|
|
||||||
for (i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
if (safe_id[i] == '/')
|
|
||||||
{
|
|
||||||
safe_id[i] = '-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return safe_id;
|
|
||||||
}
|
|
||||||
HashMap *
|
|
||||||
RoomEventFetchRaw(Room *room, char *id)
|
|
||||||
{
|
|
||||||
char *safe_id;
|
|
||||||
HashMap *ret;
|
|
||||||
DbRef *event_ref;
|
|
||||||
|
|
||||||
if (!room || !id)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Let's try to locally find that event in our junk. */
|
|
||||||
safe_id = CreateSafeID(id);
|
|
||||||
event_ref = DbLockIntent(
|
|
||||||
room->db, DB_HINT_READONLY,
|
|
||||||
4, "rooms", room->id, "events", safe_id
|
|
||||||
);
|
|
||||||
if (!event_ref)
|
|
||||||
{
|
|
||||||
/* TODO: Fetch from another homeserver if possible. */
|
|
||||||
ret = NULL;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
ret = JsonDuplicate(JsonValueAsObject(HashMapGet(DbJson(event_ref), "pdu")));
|
|
||||||
|
|
||||||
DbUnlock(room->db, event_ref);
|
|
||||||
finish:
|
|
||||||
Free(safe_id);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
HashMap *
|
|
||||||
RoomEventFetch(Room *room, char *id, bool prev)
|
|
||||||
{
|
|
||||||
char *safe_id, *redactor;
|
|
||||||
HashMap *ret, *unsign;
|
|
||||||
DbRef *event_ref;
|
|
||||||
uint64_t ts;
|
|
||||||
|
|
||||||
if (!room || !id)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Let's try to locally find that event in our junk. */
|
|
||||||
safe_id = CreateSafeID(id);
|
|
||||||
event_ref = DbLockIntent(
|
|
||||||
room->db, DB_HINT_READONLY,
|
|
||||||
4, "rooms", room->id, "events", safe_id
|
|
||||||
);
|
|
||||||
if (!event_ref)
|
|
||||||
{
|
|
||||||
/* TODO: Fetch from another homeserver if possible. */
|
|
||||||
ret = NULL;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
ret = JsonDuplicate(JsonValueAsObject(HashMapGet(DbJson(event_ref), "pdu")));
|
|
||||||
unsign = JsonValueAsObject(HashMapGet(ret, "unsigned"));
|
|
||||||
|
|
||||||
/* Overwrite a few unsigned properties on the fly. */
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"next_events",
|
|
||||||
JsonValueDuplicate(HashMapGet(DbJson(event_ref), "next_events"))
|
|
||||||
));
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"pdu_status",
|
|
||||||
JsonValueDuplicate(HashMapGet(DbJson(event_ref), "status"))
|
|
||||||
));
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"transaction_id",
|
|
||||||
JsonValueDuplicate(HashMapGet(DbJson(event_ref), "transaction"))
|
|
||||||
));
|
|
||||||
ts = JsonValueAsInteger(HashMapGet(ret, "origin_server_ts"));
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"age",
|
|
||||||
JsonValueInteger(UtilTsMillis() - ts)
|
|
||||||
));
|
|
||||||
|
|
||||||
if (prev)
|
|
||||||
{
|
|
||||||
redactor =
|
|
||||||
JsonValueAsString(HashMapGet(DbJson(event_ref), "redacted_by"));
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"redacted_because",
|
|
||||||
JsonValueObject(RoomEventFetch(room, redactor, false))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev && HashMapGet(ret, "state_key"))
|
|
||||||
{
|
|
||||||
char *prev_ev;
|
|
||||||
HashMap *prev_obj;
|
|
||||||
JsonValue *prev_content;
|
|
||||||
prev_ev =
|
|
||||||
JsonValueAsString(HashMapGet(DbJson(event_ref), "prev_event"));
|
|
||||||
prev_obj = RoomEventFetch(room, prev_ev, false);
|
|
||||||
prev_content = HashMapGet(prev_obj, "content");
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
unsign,
|
|
||||||
"prev_content",
|
|
||||||
JsonValueDuplicate(prev_content)
|
|
||||||
));
|
|
||||||
|
|
||||||
JsonFree(prev_obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
DbUnlock(room->db, event_ref);
|
|
||||||
finish:
|
|
||||||
Free(safe_id);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMap *
|
|
||||||
RoomEventCreate(char *sender, char *type, char *key, HashMap *c, char *txn)
|
|
||||||
{
|
|
||||||
HashMap *event;
|
|
||||||
if (!sender || !type || !c)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
event = HashMapCreate();
|
|
||||||
JsonSet(event, JsonValueObject(c), 1, "content");
|
|
||||||
JsonSet(event, JsonValueString(sender), 1, "sender");
|
|
||||||
JsonSet(event, JsonValueString(type), 1, "type");
|
|
||||||
JsonSet(event, JsonValueString(txn), 1, "transaction");
|
|
||||||
if (key)
|
|
||||||
{
|
|
||||||
JsonSet(event, JsonValueString(key), 1, "state_key");
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
RoomAddAlias(Db *db, char *roomAlias, char *roomId, char *sender, char *serv)
|
|
||||||
{
|
|
||||||
DbRef *aliasRef;
|
|
||||||
HashMap *json, *alias, *idObject;
|
|
||||||
Array *servers, *ids;
|
|
||||||
if (!db || !roomAlias || !roomId || !sender || !serv)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
aliasRef = DbLock(db, 1, "aliases");
|
|
||||||
if (!aliasRef)
|
|
||||||
{
|
|
||||||
aliasRef = DbCreate(db, 1, "aliases");
|
|
||||||
if (!aliasRef)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json = DbJson(aliasRef);
|
|
||||||
|
|
||||||
servers = ArrayCreate();
|
|
||||||
ArrayAdd(servers, JsonValueString(serv));
|
|
||||||
|
|
||||||
/* alias => ID */
|
|
||||||
alias = HashMapCreate();
|
|
||||||
JsonSet(alias, JsonValueString(roomId), 1, "id");
|
|
||||||
JsonSet(alias, JsonValueString(sender), 1, "createdBy");
|
|
||||||
JsonSet(alias, JsonValueArray(servers), 1, "servers");
|
|
||||||
|
|
||||||
JsonValueFree(
|
|
||||||
JsonSet(json, JsonValueObject(alias), 2, "aliases", roomAlias));
|
|
||||||
|
|
||||||
/* ID => alias(es) */
|
|
||||||
if (!(idObject =
|
|
||||||
JsonValueAsObject(JsonGet(json, 2, "id", roomId))))
|
|
||||||
{
|
|
||||||
ids = ArrayCreate();
|
|
||||||
idObject = HashMapCreate();
|
|
||||||
HashMapSet(idObject, "aliases", JsonValueArray(ids));
|
|
||||||
JsonSet(
|
|
||||||
json,
|
|
||||||
JsonValueObject(idObject),
|
|
||||||
2, "id", roomId);
|
|
||||||
}
|
|
||||||
ids = JsonValueAsArray(HashMapGet(idObject, "aliases"));
|
|
||||||
ArrayAdd(ids, JsonValueString(roomAlias));
|
|
||||||
|
|
||||||
DbUnlock(db, aliasRef);
|
|
||||||
}
|
|
||||||
char *
|
|
||||||
RoomResolveAlias(Db *db, char *roomAlias)
|
|
||||||
{
|
|
||||||
DbRef *aliasRef;
|
|
||||||
HashMap *json;
|
|
||||||
char *ret;
|
|
||||||
if (!db || !roomAlias)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
aliasRef = DbLock(db, 1, "aliases");
|
|
||||||
if (!aliasRef)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
json = DbJson(aliasRef);
|
|
||||||
ret = JsonValueAsString(JsonGet(json, 3, "aliases", roomAlias, "id"));
|
|
||||||
ret = StrDuplicate(ret);
|
|
||||||
|
|
||||||
DbUnlock(db, aliasRef);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
Array *
|
|
||||||
RoomReverseAlias(Db *db, char *roomId)
|
|
||||||
{
|
|
||||||
DbRef *aliasRef;
|
|
||||||
HashMap *json, *idObject;
|
|
||||||
Array *ret, *original;
|
|
||||||
size_t i;
|
|
||||||
if (!db || !roomId)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
aliasRef = DbLock(db, 1, "aliases");
|
|
||||||
if (!aliasRef)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
json = DbJson(aliasRef);
|
|
||||||
if (!(idObject =
|
|
||||||
JsonValueAsObject(JsonGet(json, 2, "id", roomId))))
|
|
||||||
{
|
|
||||||
DbUnlock(db, aliasRef);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
original = JsonValueAsArray(HashMapGet(idObject, "aliases"));
|
|
||||||
ret = ArrayCreate();
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(original); i++)
|
|
||||||
{
|
|
||||||
JsonValue *v = ArrayGet(original, i);
|
|
||||||
ArrayAdd(ret, StrDuplicate(JsonValueAsString(v)));
|
|
||||||
}
|
|
||||||
|
|
||||||
DbUnlock(db, aliasRef);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
RoomFreeReverse(Array *arr)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
if (!arr)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (i = 0; i < ArraySize(arr); i++)
|
|
||||||
{
|
|
||||||
Free(ArrayGet(arr, i));
|
|
||||||
}
|
|
||||||
ArrayFree(arr);
|
|
||||||
}
|
|
||||||
void
|
|
||||||
RoomSendInvite(User *sender, bool direct, char *user, Room *room)
|
|
||||||
{
|
|
||||||
HashMap *content, *event;
|
|
||||||
CommonID *senderID;
|
|
||||||
char *senderStr;
|
|
||||||
Config conf;
|
|
||||||
if (!sender || !user || !room)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ConfigLock(room->db, &conf);
|
|
||||||
senderID = UserIdParse(UserGetName(sender), conf.serverName);
|
|
||||||
senderID->sigil = '@';
|
|
||||||
senderStr = ParserRecomposeCommonID(*senderID);
|
|
||||||
UserIdFree(senderID);
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueBoolean(direct), 1, "is_direct");
|
|
||||||
JsonSet(content, JsonValueString("invite"), 1, "membership");
|
|
||||||
event = RoomEventCreate(senderStr, "m.room.member", user, content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
/* TODO: Send to "local" invite list if user is local.
|
|
||||||
* Otherwise, ping the entire world about it. */
|
|
||||||
|
|
||||||
ConfigUnlock(&conf);
|
|
||||||
Free(senderStr);
|
|
||||||
}
|
|
||||||
Db *
|
|
||||||
RoomGetDB(Room *room)
|
|
||||||
{
|
{
|
||||||
if (!room)
|
if (!room)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return room->db;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomContainsUser(Room *room, char *user)
|
|
||||||
{
|
|
||||||
State *state;
|
|
||||||
bool ret;
|
|
||||||
if (!room || !user)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = StateCurrent(room);
|
|
||||||
|
|
||||||
ret = RoomUserHasMembership(room, state, user, "join");
|
|
||||||
|
|
||||||
StateFree(state);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomCanJoin(Room *room, char *user)
|
|
||||||
{
|
|
||||||
State *state;
|
|
||||||
HashMap *joinRule = NULL;
|
|
||||||
char *joinRuleV;
|
|
||||||
bool ret;
|
|
||||||
if (!room || !user)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = StateCurrent(room);
|
|
||||||
|
|
||||||
/* No rooms for banned people! */
|
|
||||||
if (RoomUserHasMembership(room, state, user, "ban"))
|
|
||||||
{
|
|
||||||
ret = false;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check join_rules */
|
|
||||||
joinRule = RoomEventFetch(
|
|
||||||
room,
|
|
||||||
StateGet(state, "m.room.join_rules", ""), false
|
|
||||||
);
|
|
||||||
joinRuleV = JsonValueAsString(JsonGet(joinRule, 2, "content", "join_rule"));
|
|
||||||
|
|
||||||
if (StrEquals(joinRuleV, "public"))
|
|
||||||
{
|
|
||||||
/* Anyone can join the room without any prior action. */
|
|
||||||
ret = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
if (StrEquals(joinRuleV, "invite"))
|
|
||||||
{
|
|
||||||
/* A user must first receive an invite from someone already in the
|
|
||||||
* room in order to join. */
|
|
||||||
ret = RoomUserHasMembership(room, state, user, "invite");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StrEquals(joinRuleV, "knock"))
|
|
||||||
{
|
|
||||||
/* TODO: Knocking and restricted rooms. */
|
|
||||||
ret = false;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
if (StrEquals(joinRuleV, "restricted"))
|
|
||||||
{
|
|
||||||
/* TODO: Knocking and restricted rooms. */
|
|
||||||
ret = false;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
if (StrEquals(joinRuleV, "knock_restricted"))
|
|
||||||
{
|
|
||||||
/* TODO: Knocking and restricted rooms. */
|
|
||||||
ret = false;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* All other rooms are considered private. */
|
|
||||||
ret = false;
|
|
||||||
end:
|
|
||||||
StateFree(state);
|
|
||||||
JsonFree(joinRule);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
RoomLeave(Room *room, User *user, char **errp)
|
|
||||||
{
|
|
||||||
CommonID *userId = NULL;
|
|
||||||
char *userString = NULL;
|
|
||||||
char *server = NULL;
|
|
||||||
HashMap *content = NULL;
|
|
||||||
HashMap *event = NULL;
|
|
||||||
HashMap *pdu = NULL;
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if (!room || !user)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = ConfigGetServerName(room->db);
|
|
||||||
if (!server)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
userId = UserIdParse(UserGetName(user), server);
|
|
||||||
userId->sigil = '@';
|
|
||||||
userString = ParserRecomposeCommonID(*userId);
|
|
||||||
Free(server);
|
|
||||||
server = NULL;
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString("leave"), 1, "membership");
|
|
||||||
event = RoomEventCreate(userString, "m.room.member", userString, content, NULL);
|
|
||||||
pdu = RoomEventSend(room, event, errp);
|
|
||||||
|
|
||||||
/* TODO: One ought to be extremely careful with managing users in those
|
|
||||||
* scenarios, as the DB flushes do not sync. */
|
|
||||||
ret = !!pdu;
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
UserRemoveJoin(user, room->id);
|
|
||||||
}
|
|
||||||
UserIdFree(userId);
|
|
||||||
JsonFree(event);
|
|
||||||
JsonFree(pdu);
|
|
||||||
if (userString)
|
|
||||||
{
|
|
||||||
Free(userString);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
char *
|
|
||||||
RoomRedact(Room *room, User *user, char *eventID, char *reason, char **errp)
|
|
||||||
{
|
|
||||||
CommonID *userId = NULL;
|
|
||||||
char *userString = NULL;
|
|
||||||
char *server = NULL;
|
|
||||||
HashMap *event = NULL;
|
|
||||||
HashMap *content = NULL;
|
|
||||||
HashMap *pdu = NULL;
|
|
||||||
char *ret = NULL;
|
|
||||||
|
|
||||||
if (!room || !user || !eventID)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = ConfigGetServerName(room->db);
|
|
||||||
if (!server)
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Could not retrieve servername.";
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
userId = UserIdParse(UserGetName(user), server);
|
|
||||||
userId->sigil = '@';
|
|
||||||
userString = ParserRecomposeCommonID(*userId);
|
|
||||||
Free(server);
|
|
||||||
server = NULL;
|
|
||||||
|
|
||||||
if (!RoomContainsUser(room, userString))
|
|
||||||
{
|
|
||||||
ret = NULL;
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "User is not already in-room.";
|
|
||||||
}
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
HashMapSet(content, "reason", JsonValueString(reason));
|
|
||||||
event = RoomEventCreate(userString,
|
|
||||||
"m.room.redaction", NULL,
|
|
||||||
content, NULL
|
|
||||||
);
|
|
||||||
HashMapSet(event, "redacts", JsonValueString(eventID));
|
|
||||||
pdu = RoomEventSend(room, event, errp);
|
|
||||||
|
|
||||||
ret = StrDuplicate(JsonValueAsString(HashMapGet(pdu, "event_id")));
|
|
||||||
if (errp && !*errp && !ret)
|
|
||||||
{
|
|
||||||
*errp = "Unknown error when redacting.";
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
UserIdFree(userId);
|
|
||||||
JsonFree(event);
|
|
||||||
JsonFree(pdu);
|
|
||||||
if (userString)
|
|
||||||
{
|
|
||||||
Free(userString);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomJoin(Room *room, User *user, char **errp)
|
|
||||||
{
|
|
||||||
CommonID *userId = NULL;
|
|
||||||
char *userString = NULL;
|
|
||||||
char *server = NULL;
|
|
||||||
HashMap *content = NULL;
|
|
||||||
HashMap *event = NULL;
|
|
||||||
HashMap *pdu = NULL;
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if (!room || !user)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = ConfigGetServerName(room->db);
|
|
||||||
if (!server)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
userId = UserIdParse(UserGetName(user), server);
|
|
||||||
userId->sigil = '@';
|
|
||||||
userString = ParserRecomposeCommonID(*userId);
|
|
||||||
Free(server);
|
|
||||||
server = NULL;
|
|
||||||
|
|
||||||
if (!RoomCanJoin(room, userString))
|
|
||||||
{
|
|
||||||
ret = false;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString("join"), 1, "membership");
|
|
||||||
event = RoomEventCreate(userString, "m.room.member", userString, content, NULL);
|
|
||||||
pdu = RoomEventSend(room, event, errp);
|
|
||||||
|
|
||||||
/* TODO: One ought to be extremely careful with managing users in those
|
|
||||||
* scenarios, as the DB flushes do not sync. */
|
|
||||||
ret = !!pdu;
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
UserAddJoin(user, room->id);
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
UserIdFree(userId);
|
|
||||||
JsonFree(event);
|
|
||||||
JsonFree(pdu);
|
|
||||||
if (userString)
|
|
||||||
{
|
|
||||||
Free(userString);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
HashMap *
|
|
||||||
RoomEventClientify(HashMap *pdu)
|
|
||||||
{
|
|
||||||
HashMap *event;
|
|
||||||
if (!pdu)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
event = HashMapCreate();
|
|
||||||
#define CopyField(field, req) do { \
|
|
||||||
JsonValue *f = HashMapGet(pdu, field);\
|
|
||||||
if (!f && req) \
|
|
||||||
{ \
|
|
||||||
goto failure; \
|
|
||||||
} \
|
|
||||||
HashMapSet( \
|
|
||||||
event, \
|
|
||||||
field, JsonValueDuplicate(f) \
|
|
||||||
); \
|
|
||||||
} \
|
|
||||||
while(0)
|
|
||||||
|
|
||||||
CopyField("content", true);
|
|
||||||
CopyField("event_id", true);
|
|
||||||
CopyField("origin_server_ts", true);
|
|
||||||
CopyField("room_id", true);
|
|
||||||
CopyField("sender", true);
|
|
||||||
CopyField("type", true);
|
|
||||||
|
|
||||||
CopyField("state_key",false);
|
|
||||||
CopyField("redacts", false);
|
|
||||||
CopyField("unsigned", false);
|
|
||||||
|
|
||||||
return event;
|
|
||||||
|
|
||||||
failure:
|
|
||||||
JsonFree(event);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
#undef CopyField
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
char *
|
|
||||||
RoomIdGet(Room * room)
|
|
||||||
{
|
|
||||||
return room ? room->id : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
RoomVersionGet(Room * room)
|
|
||||||
{
|
|
||||||
return room ? room->version : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerPart
|
|
||||||
RoomGetCreator(Room *room)
|
|
||||||
{
|
|
||||||
ServerPart ret = { 0 };
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return room->creator;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array *
|
|
||||||
RoomPrevEventsGet(Room *room)
|
|
||||||
{
|
|
||||||
HashMap *json;
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
json = DbJson(room->leaves_ref);
|
|
||||||
return JsonValueAsArray(JsonGet(json, 1, "leaves"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: This has it's fair share of problems, as a malicious
|
|
||||||
* homeserver could *easily* fake a PDU depth and put this
|
|
||||||
* function to the depth limit. */
|
|
||||||
uint64_t
|
|
||||||
RoomGetDepth(Room *room)
|
|
||||||
{
|
|
||||||
Array *leaves;
|
|
||||||
HashMap *pdu;
|
|
||||||
size_t i;
|
|
||||||
uint64_t max;
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves = RoomPrevEventsGet(room);
|
|
||||||
if (!leaves)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
max = 0;
|
|
||||||
for (i = 0; i < ArraySize(leaves); i++)
|
|
||||||
{
|
|
||||||
uint64_t depth;
|
|
||||||
pdu = JsonValueAsObject(ArrayGet(leaves, i));
|
|
||||||
|
|
||||||
depth = JsonValueAsInteger(HashMapGet(pdu, "depth"));
|
|
||||||
if (depth > max)
|
|
||||||
{
|
|
||||||
max = depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max;
|
|
||||||
}
|
|
|
@ -1,286 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Array.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Base64.h>
|
|
||||||
#include <Cytoplasm/Util.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
#include <Cytoplasm/Log.h>
|
|
||||||
#include <Cytoplasm/Db.h>
|
|
||||||
|
|
||||||
#include <Schema/RoomCreateRequest.h>
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
#include <Schema/PduV3.h>
|
|
||||||
|
|
||||||
#include <CanonicalJson.h>
|
|
||||||
#include <Config.h>
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <State.h>
|
|
||||||
#include <Event.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
|
|
||||||
{
|
|
||||||
CommonID sender;
|
|
||||||
char *sender_str, *key, *version;
|
|
||||||
HashMap *content, *event, *override, *pl_content;
|
|
||||||
Array *initial_states;
|
|
||||||
JsonValue *val;
|
|
||||||
int64_t pl = 100;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
char *join_rules_preset = NULL;
|
|
||||||
char *history_visibility_preset = NULL;
|
|
||||||
char *guest_access_preset = NULL;
|
|
||||||
bool trusted_room = false;
|
|
||||||
|
|
||||||
sender.sigil = '@';
|
|
||||||
sender.local = UserGetName(user);
|
|
||||||
sender.server = s;
|
|
||||||
sender_str = ParserRecomposeCommonID(sender);
|
|
||||||
|
|
||||||
/* m.room.create */
|
|
||||||
content = HashMapCreate();
|
|
||||||
if (room->version <= 10)
|
|
||||||
{
|
|
||||||
JsonSet(content, JsonValueString(sender_str), 1, "creator");
|
|
||||||
}
|
|
||||||
|
|
||||||
version = req->room_version ?
|
|
||||||
StrDuplicate(req->room_version) : StrInt(room->version);
|
|
||||||
JsonSet(content, JsonValueString(version), 1, "room_version");
|
|
||||||
Free(version);
|
|
||||||
|
|
||||||
while (HashMapIterate(req->creation_content, &key, (void **) &val))
|
|
||||||
{
|
|
||||||
JsonValue *content_v = HashMapGet(content, key);
|
|
||||||
if (content_v &&
|
|
||||||
!StrEquals(key, "creator") &&
|
|
||||||
!StrEquals(key, "room_version"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
JsonValueFree(HashMapSet(content, key, JsonValueDuplicate(val)));
|
|
||||||
}
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.create", "", content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
UserAddJoin(user, room->id);
|
|
||||||
|
|
||||||
/* m.room.member */
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString("join"), 1, "membership");
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.member", sender_str, content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
/* m.room.power_levels */
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(
|
|
||||||
content,
|
|
||||||
JsonValueInteger(pl),
|
|
||||||
2, "users", sender_str);
|
|
||||||
override = req->power_level_content_override;
|
|
||||||
while (override && HashMapIterate(override, &key, (void **) &val))
|
|
||||||
{
|
|
||||||
JsonValue *main = HashMapGet(content, key);
|
|
||||||
if (main)
|
|
||||||
{
|
|
||||||
HashMapDelete(content, key);
|
|
||||||
JsonValueFree(main);
|
|
||||||
}
|
|
||||||
HashMapSet(content, key, JsonValueDuplicate(val));
|
|
||||||
}
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.power_levels", "", content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
/* Presets */
|
|
||||||
switch (req->preset)
|
|
||||||
{
|
|
||||||
case ROOM_CREATE_PUBLIC:
|
|
||||||
join_rules_preset = "public";
|
|
||||||
history_visibility_preset = "shared";
|
|
||||||
guest_access_preset = "forbidden";
|
|
||||||
break;
|
|
||||||
case ROOM_CREATE_TRUSTED:
|
|
||||||
trusted_room = true;
|
|
||||||
/* Fallthrough */
|
|
||||||
case ROOM_CREATE_PRIVATE:
|
|
||||||
join_rules_preset = "invite";
|
|
||||||
history_visibility_preset = "shared";
|
|
||||||
guest_access_preset = "can_join";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
switch (req->visibility)
|
|
||||||
{
|
|
||||||
case ROOM_PUBLIC:
|
|
||||||
join_rules_preset = "public";
|
|
||||||
history_visibility_preset = "shared";
|
|
||||||
guest_access_preset = "forbidden";
|
|
||||||
break;
|
|
||||||
case ROOM_PRIVATE:
|
|
||||||
join_rules_preset = "invite";
|
|
||||||
history_visibility_preset = "shared";
|
|
||||||
guest_access_preset = "can_join";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write out presets */
|
|
||||||
#define SetIfExistent(p,a) do { \
|
|
||||||
if (p##_preset) \
|
|
||||||
{ \
|
|
||||||
content = HashMapCreate(); \
|
|
||||||
JsonSet( \
|
|
||||||
content, \
|
|
||||||
JsonValueString(p##_preset) \
|
|
||||||
, 1, a); \
|
|
||||||
event = RoomEventCreate( \
|
|
||||||
sender_str, \
|
|
||||||
"m.room." #p, "", content, NULL); \
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL)); \
|
|
||||||
JsonFree(event); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while (0)
|
|
||||||
|
|
||||||
SetIfExistent(join_rules, "join_rule");
|
|
||||||
SetIfExistent(history_visibility, "history_visibility");
|
|
||||||
SetIfExistent(guest_access, "guest_access");
|
|
||||||
#undef SetIfExistent
|
|
||||||
|
|
||||||
/* User-provided initial states */
|
|
||||||
initial_states = req->initial_state;
|
|
||||||
for (i = 0; i < ArraySize(initial_states); i++)
|
|
||||||
{
|
|
||||||
RoomStateEvent *rse = ArrayGet(initial_states, i);
|
|
||||||
HashMap *rseObject = RoomStateEventToJson(rse);
|
|
||||||
|
|
||||||
if (!HashMapGet(rseObject, "state_key"))
|
|
||||||
{
|
|
||||||
HashMapSet(rseObject, "state_key", JsonValueString(""));
|
|
||||||
}
|
|
||||||
HashMapSet(rseObject, "sender", JsonValueString(sender_str));
|
|
||||||
|
|
||||||
JsonFree(RoomEventSend(room, rseObject, NULL));
|
|
||||||
JsonFree(rseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Name and topic. */
|
|
||||||
if (req->name)
|
|
||||||
{
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString(req->name), 1, "name");
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.name", "", content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
}
|
|
||||||
if (req->topic)
|
|
||||||
{
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString(req->topic), 1, "topic");
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.topic", "", content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom alias */
|
|
||||||
if (req->room_alias_name && !RoomResolveAlias(room->db, room->id))
|
|
||||||
{
|
|
||||||
CommonID full;
|
|
||||||
char *fullStr, *serverStr;
|
|
||||||
|
|
||||||
full.sigil = '#';
|
|
||||||
full.local = req->room_alias_name;
|
|
||||||
full.server = s;
|
|
||||||
fullStr = ParserRecomposeCommonID(full);
|
|
||||||
|
|
||||||
serverStr = ParserRecomposeServerPart(room->creator);
|
|
||||||
|
|
||||||
RoomAddAlias(room->db, fullStr, room->id, sender_str, serverStr);
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
JsonSet(content, JsonValueString(fullStr), 1, "alias");
|
|
||||||
event = RoomEventCreate(
|
|
||||||
sender_str,
|
|
||||||
"m.room.canonical_alias", "", content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
Free(fullStr);
|
|
||||||
Free(serverStr);
|
|
||||||
}
|
|
||||||
/* Invites */
|
|
||||||
pl_content = HashMapCreate();
|
|
||||||
JsonSet(pl_content, JsonValueInteger(pl), 2, "users", sender_str);
|
|
||||||
for (i = 0; i < ArraySize(req->invite); i++)
|
|
||||||
{
|
|
||||||
char *user_id = ArrayGet(req->invite, i);
|
|
||||||
|
|
||||||
if (!user_id || !ValidCommonID(user_id, '@'))
|
|
||||||
{
|
|
||||||
/* TODO: Raise error. */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomSendInvite(user, req->is_direct, user_id, room);
|
|
||||||
|
|
||||||
if (trusted_room)
|
|
||||||
{
|
|
||||||
JsonValue *own = JsonValueInteger(pl);
|
|
||||||
JsonSet(pl_content, own, 2, "users", user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
event = RoomEventCreate(sender_str, "m.room.power_levels", "", pl_content, NULL);
|
|
||||||
JsonFree(RoomEventSend(room, event, NULL));
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
JsonValueFree(JsonSet(
|
|
||||||
DbJson(room->leaves_ref),
|
|
||||||
JsonValueBoolean(req->is_direct), 1, "is_direct"));
|
|
||||||
/* TODO: The rest of the events mandated by the specification on
|
|
||||||
* POST /createRoom, and error management. */
|
|
||||||
Free(sender_str);
|
|
||||||
|
|
||||||
/* TODO: Error management (and invite_3pid, later) */
|
|
||||||
}
|
|
||||||
|
|
266
src/Room/State.c
266
src/Room/State.c
|
@ -1,266 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <State.h>
|
|
||||||
|
|
||||||
State *
|
|
||||||
RoomStateGetID(Room * room, char *event_id)
|
|
||||||
{
|
|
||||||
HashMap *event;
|
|
||||||
State *state;
|
|
||||||
if (!room || !event_id)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
event = RoomEventFetch(room, event_id, false);
|
|
||||||
if (!event)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = StateResolve(room, event);
|
|
||||||
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static char *
|
|
||||||
GetCurrentMembership(Room *room, State *s, char *mxid)
|
|
||||||
{
|
|
||||||
State *state;
|
|
||||||
HashMap *event;
|
|
||||||
char *event_id;
|
|
||||||
|
|
||||||
char *ret;
|
|
||||||
|
|
||||||
if (!room || !mxid)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s)
|
|
||||||
{
|
|
||||||
state = s;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = StateCurrent(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
event_id = StateGet(state, "m.room.member", mxid);
|
|
||||||
event = RoomEventFetchRaw(room, event_id);
|
|
||||||
|
|
||||||
if (!s)
|
|
||||||
{
|
|
||||||
StateFree(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = StrDuplicate(
|
|
||||||
JsonValueAsString(JsonGet(event, 2, "content", "membership"))
|
|
||||||
);
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomIsEventVisible(Room *room, User *user, char *eventId)
|
|
||||||
{
|
|
||||||
State *state;
|
|
||||||
HashMap *entryV;
|
|
||||||
char *visibility;
|
|
||||||
char *membership, *currentMembership;
|
|
||||||
|
|
||||||
CommonID cid;
|
|
||||||
char *server;
|
|
||||||
char *mxid;
|
|
||||||
|
|
||||||
bool ret = false;
|
|
||||||
if (!room || !user || !eventId)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server = ConfigGetServerName(room->db);
|
|
||||||
cid.sigil = '@';
|
|
||||||
cid.local = UserGetName(user);
|
|
||||||
ParseServerPart(server, &cid.server);
|
|
||||||
mxid = ParserRecomposeCommonID(cid);
|
|
||||||
Free(server);
|
|
||||||
|
|
||||||
state = RoomStateGetID(room, eventId);
|
|
||||||
|
|
||||||
entryV = RoomEventFetchRaw(room,
|
|
||||||
StateGet(state, "m.room.history_visibility", "")
|
|
||||||
);
|
|
||||||
if (!(visibility = JsonValueAsString(
|
|
||||||
JsonGet(entryV, 2, "content", "history_visibility"))))
|
|
||||||
{
|
|
||||||
visibility = "shared";
|
|
||||||
}
|
|
||||||
visibility = StrDuplicate(visibility);
|
|
||||||
JsonFree(entryV);
|
|
||||||
|
|
||||||
membership = GetCurrentMembership(room, state, mxid);
|
|
||||||
currentMembership = GetCurrentMembership(room, NULL, mxid);
|
|
||||||
StateFree(state);
|
|
||||||
|
|
||||||
/* TODO: (for shared) "and the user joined the room at _any_
|
|
||||||
* point after the event was sent, allow."
|
|
||||||
* Aargh. *Why?* */
|
|
||||||
if (StrEquals(visibility, "world_readable"))
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
else if (StrEquals(membership, "join"))
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
else if (StrEquals(visibility, "shared") &&
|
|
||||||
StrEquals(currentMembership, "join")) /* Check the current
|
|
||||||
* membership, as a hack */
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
else if (StrEquals(visibility, "invited") &&
|
|
||||||
StrEquals(membership, "invite"))
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Free(mxid);
|
|
||||||
Free(membership);
|
|
||||||
Free(visibility);
|
|
||||||
Free(currentMembership);
|
|
||||||
ServerPartFree(cid.server);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PrepareState(room, S, type, key, n) \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
id_##n = StateGet(S, type, key); \
|
|
||||||
if (!id_##n) \
|
|
||||||
{ \
|
|
||||||
goto finish; \
|
|
||||||
} \
|
|
||||||
n = RoomEventFetchRaw(room, id_##n); \
|
|
||||||
if (!n) \
|
|
||||||
{ \
|
|
||||||
goto finish; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while (0)
|
|
||||||
#define FinishState(name) \
|
|
||||||
if (name) \
|
|
||||||
{ \
|
|
||||||
JsonFree(name); \
|
|
||||||
} \
|
|
||||||
return ret
|
|
||||||
bool
|
|
||||||
RoomIsJoinRule(Room * room, State *state, char *jr)
|
|
||||||
{
|
|
||||||
HashMap *joinrule = NULL;
|
|
||||||
char *id_joinrule = NULL;
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if (!room || !state || !jr)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareState(room, state, "m.room.join_rules", "", joinrule);
|
|
||||||
ret = StrEquals(
|
|
||||||
JsonValueAsString(JsonGet(joinrule, 2, "content", "join_rule")),
|
|
||||||
jr);
|
|
||||||
finish:
|
|
||||||
FinishState(joinrule);
|
|
||||||
}
|
|
||||||
/* Verifies if user has a specific membership before [e_id] in the room. */
|
|
||||||
bool
|
|
||||||
RoomUserHasMembership(Room * room, State *state, char *user, char *mbr)
|
|
||||||
{
|
|
||||||
HashMap *membership = NULL;
|
|
||||||
char *id_membership = NULL;
|
|
||||||
char *membership_value;
|
|
||||||
bool ret = false;
|
|
||||||
if (!room || !state || !user || !mbr)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareState(room, state, "m.room.member", user, membership);
|
|
||||||
|
|
||||||
membership_value =
|
|
||||||
JsonValueAsString(JsonGet(membership, 2, "content", "membership"));
|
|
||||||
|
|
||||||
ret = StrEquals(membership_value, mbr);
|
|
||||||
|
|
||||||
finish:
|
|
||||||
FinishState(membership);
|
|
||||||
}
|
|
||||||
/* Computes the smallest PL needed to do something somewhere */
|
|
||||||
int64_t
|
|
||||||
RoomMinPL(Room * room, State *state, char *type, char *act)
|
|
||||||
{
|
|
||||||
HashMap *pl = NULL;
|
|
||||||
JsonValue *val;
|
|
||||||
char *id_pl;
|
|
||||||
int64_t ret = 0, def = 0;
|
|
||||||
if (!room || !state || !act)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareState(room, state, "m.room.power_levels", "", pl);
|
|
||||||
|
|
||||||
/* Every other act has a minimum PL of 0 */
|
|
||||||
def = 0;
|
|
||||||
if (StrEquals(act, "ban") ||
|
|
||||||
StrEquals(act, "kick") ||
|
|
||||||
StrEquals(act, "redact") ||
|
|
||||||
StrEquals(act, "state_default") ||
|
|
||||||
(StrEquals(type, "notifications") && StrEquals(act, "room")))
|
|
||||||
{
|
|
||||||
def = 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!type)
|
|
||||||
{
|
|
||||||
val = JsonGet(pl, 2, "content", act);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
val = JsonGet(pl, 3, "content", type, act);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = ParsePL(val, def);
|
|
||||||
finish:
|
|
||||||
FinishState(pl);
|
|
||||||
}
|
|
||||||
/* Finds the power level of an user at a state. */
|
|
||||||
/* TODO: The creator should have PL100 by default. */
|
|
||||||
int64_t
|
|
||||||
RoomUserPL(Room * room, State *state, char *user)
|
|
||||||
{
|
|
||||||
HashMap *pl = NULL;
|
|
||||||
char *id_pl;
|
|
||||||
int64_t ret = 0, def = 0;
|
|
||||||
|
|
||||||
if (!room || !state || !user)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareState(room, state, "m.room.power_levels", "", pl);
|
|
||||||
|
|
||||||
def = RoomMinPL(room, state, NULL, "users_default");
|
|
||||||
ret = ParsePL(JsonGet(pl, 3, "content", "users", user), def);
|
|
||||||
|
|
||||||
finish:
|
|
||||||
FinishState(pl);
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
static bool
|
|
||||||
VerifyServers(char *id1, char *id2)
|
|
||||||
{
|
|
||||||
CommonID cid1;
|
|
||||||
CommonID cid2;
|
|
||||||
|
|
||||||
char *str1;
|
|
||||||
char *str2;
|
|
||||||
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if (!ParseCommonID(id1, &cid1))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ParseCommonID(id2, &cid2))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
str1 = ParserRecomposeServerPart(cid1.server);
|
|
||||||
str2 = ParserRecomposeServerPart(cid2.server);
|
|
||||||
|
|
||||||
if (StrEquals(str1, str2))
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
Free(str1);
|
|
||||||
Free(str2);
|
|
||||||
CommonIDFree(cid1);
|
|
||||||
CommonIDFree(cid2);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
AuthoriseAliasV1(PduV1 pdu, char **errp)
|
|
||||||
{
|
|
||||||
/* Step 4.1: If event has no state_key, reject. */
|
|
||||||
if (!pdu.state_key || StrEquals(pdu.state_key, ""))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 4.1 fail: no state key in the alias";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 4.2: If sender's domain doesn't matches state_key, reject. */
|
|
||||||
if (!VerifyServers(pdu.state_key, pdu.sender))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 4.2 fail: alias domain doesnt match statekey";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 4.3: Otherwise, allow. */
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
#include "Room/V1/Auth/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
static bool
|
|
||||||
VerifyServers(char *id1, char *id2)
|
|
||||||
{
|
|
||||||
CommonID cid1;
|
|
||||||
CommonID cid2;
|
|
||||||
|
|
||||||
char *str1;
|
|
||||||
char *str2;
|
|
||||||
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if (!ParseCommonID(id1, &cid1))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ParseCommonID(id2, &cid2))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
str1 = ParserRecomposeServerPart(cid1.server);
|
|
||||||
str2 = ParserRecomposeServerPart(cid2.server);
|
|
||||||
|
|
||||||
if (StrEquals(str1, str2))
|
|
||||||
{
|
|
||||||
ret = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
Free(str1);
|
|
||||||
Free(str2);
|
|
||||||
CommonIDFree(cid1);
|
|
||||||
CommonIDFree(cid2);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
HashMap *create_event;
|
|
||||||
char *create_event_id;
|
|
||||||
JsonValue *federate;
|
|
||||||
int64_t pdu_pl = RoomUserPL(room, state, pdu.sender);
|
|
||||||
int64_t event_pl = RoomMinPL(room,state, "events", pdu.type);
|
|
||||||
/* Step 1: If m.room.create */
|
|
||||||
|
|
||||||
if (StrEquals(pdu.type, "m.room.create"))
|
|
||||||
{
|
|
||||||
return AuthoriseCreateV1(pdu, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 2: Considering the event's auth_events.
|
|
||||||
* TODO: This is slow. */
|
|
||||||
if (!ConsiderAuthEventsV1(room, pdu, errp))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 3: If the content of the m.room.create event in the room state
|
|
||||||
* has the property m.federate set to false, and the sender domain of
|
|
||||||
* the event does not match the sender domain of the create event,
|
|
||||||
* reject.
|
|
||||||
* TODO: This is slow.
|
|
||||||
*/
|
|
||||||
create_event_id = StateGet(state, "m.room.create", "");
|
|
||||||
if (!state || !create_event_id)
|
|
||||||
{
|
|
||||||
/* At this point, [create_event_id] has to exist */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "No creation event in the state. Needless to say, "
|
|
||||||
"your room is done for.";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
create_event = RoomEventFetchRaw(room, create_event_id);
|
|
||||||
federate = JsonGet(create_event, 2, "content", "m.federate");
|
|
||||||
if (JsonValueType(federate) == JSON_BOOLEAN)
|
|
||||||
{
|
|
||||||
if (!JsonValueAsBoolean(federate))
|
|
||||||
{
|
|
||||||
char *c_sender =
|
|
||||||
JsonValueAsString(JsonGet(create_event, 1, "sender"));
|
|
||||||
char *p_sender = pdu.sender;
|
|
||||||
if (!VerifyServers(c_sender, p_sender))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Trying to access a room with m.federate off.";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonFree(create_event);
|
|
||||||
|
|
||||||
/* Step 4: If type is m.room.aliases */
|
|
||||||
if (StrEquals(pdu.type, "m.room.aliases"))
|
|
||||||
{
|
|
||||||
return AuthoriseAliasV1(pdu, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5: If type is m.room.member */
|
|
||||||
if (StrEquals(pdu.type, "m.room.member"))
|
|
||||||
{
|
|
||||||
return AuthoriseMemberV1(room, pdu, state, errp);
|
|
||||||
}
|
|
||||||
/* Step 6: If the sender's current membership state is not join, reject. */
|
|
||||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 7: If type is m.room.third_party_invite */
|
|
||||||
if (StrEquals(pdu.type, "m.room.third_party_invite"))
|
|
||||||
{
|
|
||||||
/* Allow if and only if sender's current power level is greater than
|
|
||||||
* or equal to the invite level */
|
|
||||||
int64_t min_pl = RoomMinPL(room, state, NULL, "invite");
|
|
||||||
|
|
||||||
return pdu_pl >= min_pl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 8: If the event type's required power level is greater than the
|
|
||||||
* sender's power level, reject. */
|
|
||||||
if (event_pl > pdu_pl)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step (9): If the event has a state_key that starts with an @ and does
|
|
||||||
* not match the sender, reject. */
|
|
||||||
if (pdu.state_key && *pdu.state_key == '@')
|
|
||||||
{
|
|
||||||
if (!StrEquals(pdu.state_key, pdu.sender))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 10: If type is m.room.power_levels */
|
|
||||||
if (StrEquals(pdu.type, "m.room.power_levels"))
|
|
||||||
{
|
|
||||||
return AuthorisePowerLevelsV1(room, pdu, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 11: If type is m.room.redaction */
|
|
||||||
if (StrEquals(pdu.type, "m.room.redaction"))
|
|
||||||
{
|
|
||||||
int64_t min_pl = RoomMinPL(room, state, NULL, "redact");
|
|
||||||
|
|
||||||
/* Step 11.1: If the sender's power level is greater than or equal
|
|
||||||
* to the redact level, allow. */
|
|
||||||
if (pdu_pl >= min_pl)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StrEquals(create_event_id, pdu.redacts))
|
|
||||||
{
|
|
||||||
/* This is _not_ spec behaviour. As such, I _need_ to document
|
|
||||||
* this oddity. Apparently, up until v11 came along, a user could
|
|
||||||
* redact a creation event. Freely. Without complaining. Of course,
|
|
||||||
* this breaks the room.
|
|
||||||
*
|
|
||||||
* As such, I have made the room to NOT redact creation events.
|
|
||||||
* Diverges that could be caused by this is, in my opinion low,
|
|
||||||
* and all other servers that completely listen to the spec will
|
|
||||||
* be broken. */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 11.2: If the domain of the event_id of the event being
|
|
||||||
* redacted is the same as the domain of the event_id of the
|
|
||||||
* m.room.redaction, allow. */
|
|
||||||
if (pdu.redacts && VerifyServers(pdu.redacts, pdu.event_id))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 11.3: Otherwise, reject. */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 12: Otherwise, allow. */
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
#include "Room/V1/Auth/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/Log.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
static bool
|
|
||||||
VerifyPDUV1(PduV1 *auth_pdu)
|
|
||||||
{
|
|
||||||
/* TODO: The PDU could come from an unknown source, which may lack
|
|
||||||
* the tools to verify softfailing(or we may not trust them)*/
|
|
||||||
/* TODO:
|
|
||||||
* https://spec.matrix.org/v1.7/server-server-api/
|
|
||||||
* #checks-performed-on-receipt-of-a-pdu */
|
|
||||||
if (RoomIsRejectedV1(*auth_pdu))
|
|
||||||
{
|
|
||||||
Log(LOG_ERR, "Auth PDU has been rejected.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (RoomIsSoftfailedV1(*auth_pdu))
|
|
||||||
{
|
|
||||||
Log(LOG_ERR, "Auth PDU has been softfailed.");
|
|
||||||
}
|
|
||||||
return true; /* This only shows whenever an event was rejected, not
|
|
||||||
* soft-failed */
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
|
|
||||||
{
|
|
||||||
if (IsState(auth_pdu, "m.room.create", ""))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (IsState(auth_pdu, "m.room.power_levels", ""))
|
|
||||||
{
|
|
||||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (IsState(auth_pdu, "m.room.member", pdu->sender))
|
|
||||||
{
|
|
||||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (StrEquals(pdu->type, "m.room.member"))
|
|
||||||
{
|
|
||||||
char *membership =
|
|
||||||
JsonValueAsString(JsonGet(pdu->content, 1, "membership"));
|
|
||||||
JsonValue *third_pid =
|
|
||||||
JsonGet(pdu->content, 1, "third_party_invite");
|
|
||||||
|
|
||||||
/* The PDU's state_key is the target. So we check if if the
|
|
||||||
* auth PDU would count as the target's membership. Was very fun to
|
|
||||||
* find that out when I wanted to kick my 'yukari' alt. */
|
|
||||||
if (IsState(auth_pdu, "m.room.member", pdu->state_key))
|
|
||||||
{
|
|
||||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ((StrEquals(membership, "join") ||
|
|
||||||
StrEquals(membership, "invite")) &&
|
|
||||||
IsState(auth_pdu, "m.room.join_rules",""))
|
|
||||||
{
|
|
||||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (StrEquals(membership, "invite") && third_pid)
|
|
||||||
{
|
|
||||||
HashMap *tpid = JsonValueAsObject(third_pid);
|
|
||||||
JsonValue *token =
|
|
||||||
JsonGet(tpid, 2, "signed", "token");
|
|
||||||
char *token_str = JsonValueAsString(token);
|
|
||||||
if (IsState(auth_pdu, "m.room.third_party_invite", token_str))
|
|
||||||
{
|
|
||||||
/* TODO: Check if it is the latest. */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* V1 simply doesn't have the concept of restricted rooms,
|
|
||||||
* so we can safely skip this one for this function. */
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
ConsiderAuthEventsV1(Room * room, PduV1 pdu, char **errp)
|
|
||||||
{
|
|
||||||
char *ignored;
|
|
||||||
size_t i;
|
|
||||||
bool room_create = false;
|
|
||||||
HashMap *state_keytype;
|
|
||||||
state_keytype = HashMapCreate();
|
|
||||||
for (i = 0; i < ArraySize(pdu.auth_events); i++)
|
|
||||||
{
|
|
||||||
char *event_id = JsonValueAsString(ArrayGet(pdu.auth_events, i));
|
|
||||||
HashMap *event = RoomEventFetchRaw(room, event_id);
|
|
||||||
PduV1 auth_pdu = { 0 };
|
|
||||||
|
|
||||||
char *key_type_id;
|
|
||||||
|
|
||||||
if (!PduV1FromJson(event, &auth_pdu, &ignored))
|
|
||||||
{
|
|
||||||
JsonFree(event);
|
|
||||||
HashMapFree(state_keytype);
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Couldn't parse an auth event";
|
|
||||||
}
|
|
||||||
return false; /* Yeah... we aren't doing that. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Find a better way to do this. Using HashMaps as sets
|
|
||||||
* *works*, but it's not the best of ideas here. Also, we're using
|
|
||||||
* strings to compare things, which yeah. */
|
|
||||||
key_type_id = StrConcat(3, auth_pdu.type, ",", auth_pdu.state_key);
|
|
||||||
|
|
||||||
if (HashMapGet(state_keytype, key_type_id))
|
|
||||||
{
|
|
||||||
/* Duplicate found! We actually ignore it's actual value. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Duplicate auth event was found";
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFree(event);
|
|
||||||
PduV1Free(&auth_pdu);
|
|
||||||
|
|
||||||
HashMapFree(state_keytype);
|
|
||||||
Free(key_type_id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Whenever event is valid or not really doesn't matter, as we're
|
|
||||||
* not using it's value anywhere. */
|
|
||||||
HashMapSet(state_keytype, key_type_id, event);
|
|
||||||
Free(key_type_id);
|
|
||||||
|
|
||||||
/* Step 2.2: If there are entries whose type and state_key don't
|
|
||||||
* match those specified by the auth events selection algorithm
|
|
||||||
* described in the server specification, reject. */
|
|
||||||
if (!ValidAuthEventV1(&auth_pdu, &pdu))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Invalid authevent given.";
|
|
||||||
}
|
|
||||||
JsonFree(event);
|
|
||||||
PduV1Free(&auth_pdu);
|
|
||||||
HashMapFree(state_keytype);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 2.3: If there are entries which were themselves rejected
|
|
||||||
* under the checks performed on receipt of a PDU, reject.
|
|
||||||
* TODO */
|
|
||||||
if (!VerifyPDUV1(&auth_pdu))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Event depends on rejected PDUs";
|
|
||||||
}
|
|
||||||
PduV1Free(&auth_pdu);
|
|
||||||
JsonFree(event);
|
|
||||||
HashMapFree(state_keytype);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 2.4: If there is no m.room.create event among the entries,
|
|
||||||
* reject. */
|
|
||||||
if (!room_create && IsState(&auth_pdu, "m.room.create", ""))
|
|
||||||
{
|
|
||||||
room_create = true; /* Here, we check for the opposite. */
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFree(event);
|
|
||||||
PduV1Free(&auth_pdu);
|
|
||||||
}
|
|
||||||
HashMapFree(state_keytype);
|
|
||||||
if (!room_create && errp)
|
|
||||||
{
|
|
||||||
*errp = "Room creation event was not in the PDU's auth events";
|
|
||||||
}
|
|
||||||
return room_create; /* Step 2.4 is actually done here. */
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
#include "Room/V1/Auth/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
AuthoriseCreateV1(PduV1 pdu, char **errp)
|
|
||||||
{
|
|
||||||
bool ret = true;
|
|
||||||
CommonID sender = { 0 }, room_id = { 0 };
|
|
||||||
char *sender_serv = NULL, *id_serv = NULL;
|
|
||||||
|
|
||||||
if (ArraySize(pdu.auth_events) > 0)
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Room creation event has auth events";
|
|
||||||
}
|
|
||||||
ret = false;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (!ParseCommonID(pdu.room_id, &room_id) ||
|
|
||||||
!ParseCommonID(pdu.sender, &sender))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Couldn't parse the sender/room ID";
|
|
||||||
}
|
|
||||||
ret = false;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
sender_serv = ParserRecomposeServerPart(sender.server);
|
|
||||||
id_serv = ParserRecomposeServerPart(room_id.server);
|
|
||||||
if (!StrEquals(sender_serv, id_serv))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Room is not properly namespaced";
|
|
||||||
}
|
|
||||||
ret = false;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
/* TODO: Check room_version as in step 1.3 */
|
|
||||||
if (!HashMapGet(pdu.content, "creator"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Room creation event has auth events";
|
|
||||||
}
|
|
||||||
ret = false;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
finish:
|
|
||||||
if (sender_serv)
|
|
||||||
{
|
|
||||||
Free(sender_serv);
|
|
||||||
}
|
|
||||||
if (id_serv)
|
|
||||||
{
|
|
||||||
Free(id_serv);
|
|
||||||
}
|
|
||||||
CommonIDFree(sender);
|
|
||||||
CommonIDFree(room_id);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,371 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
static bool
|
|
||||||
AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
int64_t invite_level;
|
|
||||||
int64_t pdu_level;
|
|
||||||
JsonValue *third_pi;
|
|
||||||
/* Step 5.3.1: If content has a third_party_invite property */
|
|
||||||
if ((third_pi = JsonGet(pdu.content, 1, "third_party_invite")))
|
|
||||||
{
|
|
||||||
JsonValue *signed_val, *mxid, *token;
|
|
||||||
HashMap *third_pi_obj = JsonValueAsObject(third_pi), *signed_obj;
|
|
||||||
HashMap *third_pi_event;
|
|
||||||
char *third_pi_id, *thirdpi_event_sender;
|
|
||||||
/* Step 5.3.1.1: If target user is banned, reject. */
|
|
||||||
if (RoomUserHasMembership(room, state, pdu.state_key, "ban"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.1 fail: target is banned";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 5.3.1.2: If content.third_party_invite does not have a signed
|
|
||||||
* property, reject. */
|
|
||||||
if (!(signed_val = JsonGet(third_pi_obj, 1, "signed")))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.2 fail: unsigned 3PII";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
signed_obj = JsonValueAsObject(signed_val);
|
|
||||||
|
|
||||||
/* Step 5.3.1.3: If signed does not have mxid and token properties,
|
|
||||||
* reject. */
|
|
||||||
if (!(mxid = JsonGet(signed_obj, 1, "mxid")))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.3 fail: no MXID in 3PII";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(token = JsonGet(signed_obj, 1, "token")))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.3 fail: no token in 3PII";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.3.1.4: If mxid does not match state_key, reject. */
|
|
||||||
if (!StrEquals(JsonValueAsString(mxid), pdu.state_key))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.4 fail: 3PII's MXID != state_key";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.3.1.5: If there is no m.room.third_party_invite event
|
|
||||||
* in the current room state with state_key matching token, reject. */
|
|
||||||
if (!(third_pi_id = StateGet(
|
|
||||||
state,
|
|
||||||
"m.room.third_party_invite", JsonValueAsString(token))))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.5 fail: no proper 3PII event";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
third_pi_event = RoomEventFetchRaw(room, third_pi_id);
|
|
||||||
|
|
||||||
/* Step 5.3.1.6: If sender does not match sender of the
|
|
||||||
* m.room.third_party_invite, reject. */
|
|
||||||
thirdpi_event_sender = JsonValueAsString(JsonGet(third_pi_event, 1, "sender"));
|
|
||||||
if (!StrEquals(thirdpi_event_sender, pdu.sender))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.6 fail: sender does not match 3PII";
|
|
||||||
}
|
|
||||||
JsonFree(third_pi_event);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
JsonFree(third_pi_event);
|
|
||||||
|
|
||||||
/* TODO:
|
|
||||||
* Step 5.3.1.7: If any signature in signed matches any public key in
|
|
||||||
* the m.room.third_party_invite event, allow.
|
|
||||||
*
|
|
||||||
* The public keys are in content of m.room.third_party_invite as:
|
|
||||||
* - A single public key in the public_key property.
|
|
||||||
* - A list of public keys in the public_keys property. */
|
|
||||||
|
|
||||||
/* Step 5.3.1.8: Otherwise, reject. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.1.8 fail: signature check do not match";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.3.2: If the sender's current membership state is not join,
|
|
||||||
* reject. */
|
|
||||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.2 fail: sender is not 'join'ed";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 5.3.3: If target user's current membership state is join or ban, reject. */
|
|
||||||
if (RoomUserHasMembership(room, state, pdu.state_key, "join") ||
|
|
||||||
RoomUserHasMembership(room, state, pdu.state_key, "ban"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.3 fail: target is 'join'|'ban'd";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.3.4: If the sender's power level is greater than or equal to the
|
|
||||||
* invite level, allow. */
|
|
||||||
invite_level = RoomMinPL(room, state, NULL, "invite");
|
|
||||||
pdu_level = RoomUserPL(room, state, pdu.sender);
|
|
||||||
if (pdu_level >= invite_level)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/* Step 5.3.5: Otherwise, reject. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.3.5 fail: sender has no permissions to do so";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
int64_t ban_level = RoomMinPL(room, state, NULL, "ban");
|
|
||||||
int64_t kick_level = RoomMinPL(room, state, NULL, "kick");
|
|
||||||
int64_t sender_level = RoomUserPL(room, state, pdu.sender);
|
|
||||||
int64_t target_level = RoomUserPL(room, state, pdu.state_key);
|
|
||||||
|
|
||||||
/* Step 5.4.1: If the sender matches state_key, allow if and only if
|
|
||||||
* that user's current membership state is invite or join. */
|
|
||||||
if (StrEquals(pdu.sender, pdu.state_key))
|
|
||||||
{
|
|
||||||
bool flag =
|
|
||||||
RoomUserHasMembership(room, state, pdu.sender, "invite") ||
|
|
||||||
RoomUserHasMembership(room, state, pdu.sender, "join");
|
|
||||||
if (!flag && errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.4.1 fail: user tries to leave but is "
|
|
||||||
"~'invite' AND ~'join'.";
|
|
||||||
}
|
|
||||||
return flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.4.2: If the sender's current membership state is not join,
|
|
||||||
* reject. */
|
|
||||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.4.2 fail: sender tries to kick but is "
|
|
||||||
"~'invite'.";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.4.3: If the target user's current membership state is ban,
|
|
||||||
* and the sender's power level is less than the ban level, reject. */
|
|
||||||
if (RoomUserHasMembership(room, state, pdu.state_key, "ban") &&
|
|
||||||
sender_level < ban_level)
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.4.3 fail: sender tries to unban but has no "
|
|
||||||
"permissions to do so.";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.4.4: If the sender's power level is greater than or equal to
|
|
||||||
* the kick level, and the target user's power level is less than the
|
|
||||||
* sender's power level, allow. */
|
|
||||||
if ((sender_level >= kick_level) && target_level < sender_level)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.4.5: Otherwise, reject. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.4.5 fail: sender tried to kick but has no "
|
|
||||||
"permissions to do so.";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
int64_t ban_pl, pdu_pl, target_pl;
|
|
||||||
|
|
||||||
/* Step 5.5.1: If the sender's current membership state is not join, reject. */
|
|
||||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.5.1 fail: sender tries to ban but is not "
|
|
||||||
"'join'ed";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.5.2: If the sender's power level is greater than or equal
|
|
||||||
* to the ban level, and the target user's power level is less than
|
|
||||||
* the sender's power level, allow. */
|
|
||||||
ban_pl = RoomMinPL(room, state, NULL, "ban");
|
|
||||||
pdu_pl = RoomUserPL(room, state, pdu.sender);
|
|
||||||
target_pl = RoomUserPL(room, state, pdu.state_key);
|
|
||||||
if ((pdu_pl >= ban_pl) && (target_pl < pdu_pl))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.5.3: Otherwise, reject. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.5.3 fail: sender tries to ban has no permissions to "
|
|
||||||
"do so";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
/* Step 5.2.1: If the only previous event is an m.room.create and the
|
|
||||||
* state_key is the creator, allow. */
|
|
||||||
Array *prev = pdu.prev_events;
|
|
||||||
if (ArraySize(prev) == 1)
|
|
||||||
{
|
|
||||||
/* Interperet prev properly, as a list of JsonObjects. */
|
|
||||||
char *prev_id = JsonValueAsString(ArrayGet(prev, 0));
|
|
||||||
HashMap *prev_event = RoomEventFetchRaw(room, prev_id);
|
|
||||||
bool flag = false;
|
|
||||||
|
|
||||||
if (prev_event)
|
|
||||||
{
|
|
||||||
char *type = JsonValueAsString(HashMapGet(prev_event, "type"));
|
|
||||||
char *sender = JsonValueAsString(HashMapGet(prev_event, "sender"));
|
|
||||||
if (StrEquals(type, "m.room.create") &&
|
|
||||||
StrEquals(sender, pdu.state_key))
|
|
||||||
{
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonFree(prev_event);
|
|
||||||
|
|
||||||
if (flag)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.2.2: If the sender does not match state_key, reject. */
|
|
||||||
if (!StrEquals(pdu.sender, pdu.state_key))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.2.2 fail: sender does not match statekey "
|
|
||||||
"on 'join'";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* Step 5.2.3: If the sender is banned, reject. */
|
|
||||||
if (RoomUserHasMembership(room, state, pdu.sender, "ban"))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.2.2 fail: sender is banned on 'join'";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.2.4: If the join_rule is invite then allow if membership
|
|
||||||
* state is invite or join. */
|
|
||||||
if (RoomIsJoinRule(room, state, "invite") &&
|
|
||||||
(RoomUserHasMembership(room, state, pdu.sender, "invite") ||
|
|
||||||
RoomUserHasMembership(room, state, pdu.sender, "join")))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/* Step 5.2.5: If the join_rule is public, allow. */
|
|
||||||
if (RoomIsJoinRule(room, state, "public"))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 5.2.6: Otherwise, reject. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.2.6 fail: join_rule does not allow 'join'";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AuthoriseMemberV1(Room * room, PduV1 pdu, State *state, char **errp)
|
|
||||||
{
|
|
||||||
JsonValue *membership;
|
|
||||||
char *membership_str;
|
|
||||||
/* Step 5.1: If there is no state_key property, or no membership
|
|
||||||
* property in content, reject. */
|
|
||||||
if (!pdu.state_key ||
|
|
||||||
StrEquals(pdu.state_key, "") ||
|
|
||||||
!(membership = JsonGet(pdu.content, 1, "membership")))
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.1 fail: broken membership's statekey/membership";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JsonValueType(membership) != JSON_STRING)
|
|
||||||
{
|
|
||||||
/* Also check for the type */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Step 5.1 fail: broken membership's membership";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
membership_str = JsonValueAsString(membership);
|
|
||||||
#define JumpIfMembership(mem, func) do { \
|
|
||||||
if (StrEquals(membership_str, mem)) \
|
|
||||||
{ \
|
|
||||||
return func(room, pdu, state, errp); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* Step 5.2: If membership is join. */
|
|
||||||
JumpIfMembership("join", AuthorizeJoinMembershipV1);
|
|
||||||
|
|
||||||
/* Step 5.3: If membership is invite. */
|
|
||||||
JumpIfMembership("invite", AuthorizeInviteMembershipV1);
|
|
||||||
|
|
||||||
/* Step 5.4: If membership is leave. */
|
|
||||||
JumpIfMembership("leave", AuthorizeLeaveMembershipV1);
|
|
||||||
|
|
||||||
/* Step 5.5: If membership is ban. */
|
|
||||||
JumpIfMembership("ban", AuthorizeBanMembershipV1);
|
|
||||||
|
|
||||||
/* Step 5.6: Otherwise, the membership is unknown. Reject. */
|
|
||||||
return false;
|
|
||||||
#undef JumpIfMembership
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
AuthorisePowerLevelsV1(Room * room, PduV1 pdu, State *state)
|
|
||||||
{
|
|
||||||
/* Step 10.1: If the users property in content is not an object with
|
|
||||||
* keys that are valid user IDs with values that are integers
|
|
||||||
* (or a string that is an integer), reject. */
|
|
||||||
JsonValue *users = JsonGet(pdu.content, 1, "users");
|
|
||||||
HashMap *users_o, *prev_plevent;
|
|
||||||
|
|
||||||
char *user_id, *prev_pl_id, *ev_type;
|
|
||||||
JsonValue *power_level, *ev_obj;
|
|
||||||
|
|
||||||
bool flag = true;
|
|
||||||
int64_t userpl = RoomUserPL(room, state, pdu.sender);
|
|
||||||
HashMap *event_obj;
|
|
||||||
if (JsonValueType(users) != JSON_OBJECT)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
users_o = JsonValueAsObject(users);
|
|
||||||
while (HashMapIterate(users_o, &user_id, (void **) &power_level))
|
|
||||||
{
|
|
||||||
CommonID as_cid;
|
|
||||||
if (!flag)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!ParseCommonID(user_id, &as_cid))
|
|
||||||
{
|
|
||||||
flag = false;
|
|
||||||
}
|
|
||||||
if (as_cid.sigil != '@')
|
|
||||||
{
|
|
||||||
CommonIDFree(as_cid);
|
|
||||||
flag = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify powerlevels.
|
|
||||||
* We'll use INT64_MAX as a sentinel value, as this isn't
|
|
||||||
* a valid powervalue for the specification. */
|
|
||||||
if (ParsePL(power_level, INT64_MAX) == INT64_MAX)
|
|
||||||
{
|
|
||||||
flag = false;
|
|
||||||
CommonIDFree(as_cid);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CommonIDFree(as_cid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HashMapIterate does not support breaking, so we just set a
|
|
||||||
* flag to be used. */
|
|
||||||
if (!flag)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 10.2: If there is no previous m.room.power_levels event
|
|
||||||
* in the room, allow. */
|
|
||||||
if (!(prev_pl_id = StateGet(state, "m.room.power_levels", "")))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 10.3: For the properties users_default, events_default,
|
|
||||||
* state_default, ban, redact, kick, invite, check if they were
|
|
||||||
* added, changed or removed. For each found alteration: */
|
|
||||||
prev_plevent = RoomEventFetchRaw(room, prev_pl_id);
|
|
||||||
#define CheckChange(prop) do \
|
|
||||||
{ \
|
|
||||||
JsonValue *old = \
|
|
||||||
JsonGet(prev_plevent, 2, "content", prop);\
|
|
||||||
JsonValue *new = \
|
|
||||||
JsonGet(pdu.content, 1, prop); \
|
|
||||||
int64_t oldv = 0, newv = 0; \
|
|
||||||
oldv = JsonValueAsInteger(old); \
|
|
||||||
newv = JsonValueAsInteger(new); \
|
|
||||||
if ((old && !new) || (!old && new) || \
|
|
||||||
(oldv != newv)) \
|
|
||||||
{ \
|
|
||||||
if (old && (oldv > userpl)) \
|
|
||||||
{ \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
if (new && (newv > userpl)) \
|
|
||||||
{ \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while(0)
|
|
||||||
CheckChange("users_default");
|
|
||||||
CheckChange("events_default");
|
|
||||||
CheckChange("state_default");
|
|
||||||
CheckChange("ban");
|
|
||||||
CheckChange("redact");
|
|
||||||
CheckChange("kick");
|
|
||||||
CheckChange("invite");
|
|
||||||
#undef CheckChange
|
|
||||||
#define CheckPLOld(prop) \
|
|
||||||
event_obj = \
|
|
||||||
JsonValueAsObject(JsonGet(prev_plevent, 2, "content", prop)); \
|
|
||||||
flag = true; \
|
|
||||||
while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \
|
|
||||||
{ \
|
|
||||||
JsonValue *new; \
|
|
||||||
int64_t new_pl, old_pl; \
|
|
||||||
\
|
|
||||||
if (!flag) \
|
|
||||||
{ \
|
|
||||||
continue; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
new = JsonGet(pdu.content, 2, prop, ev_type); \
|
|
||||||
old_pl = ParsePL(new, INT64_MAX); \
|
|
||||||
if (((new_pl = ParsePL(new, INT64_MAX)) != INT64_MAX) && \
|
|
||||||
((new_pl != old_pl) && old_pl > userpl)) \
|
|
||||||
{ \
|
|
||||||
flag = false; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
if (!flag) \
|
|
||||||
{ \
|
|
||||||
JsonFree(prev_plevent); \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
flag = true
|
|
||||||
#define CheckPLNew(prop) \
|
|
||||||
event_obj = \
|
|
||||||
JsonValueAsObject(JsonGet(pdu.content, 1, prop)); \
|
|
||||||
flag = true; \
|
|
||||||
while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \
|
|
||||||
{ \
|
|
||||||
JsonValue *old; \
|
|
||||||
int64_t new_pl, old_pl; \
|
|
||||||
\
|
|
||||||
if (!flag) \
|
|
||||||
{ \
|
|
||||||
continue; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
old = JsonGet(prev_plevent, 3, "content", prop, ev_type); \
|
|
||||||
new_pl = ParsePL(ev_obj, INT64_MAX); \
|
|
||||||
if (((old_pl = ParsePL(old, INT64_MAX)) != INT64_MAX && \
|
|
||||||
new_pl != old_pl) && new_pl > userpl) \
|
|
||||||
{ \
|
|
||||||
flag = false; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
if (!flag) \
|
|
||||||
{ \
|
|
||||||
JsonFree(prev_plevent); \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
flag = true
|
|
||||||
/* Step 10.4: For each entry being changed in, or removed from, the
|
|
||||||
* events property:
|
|
||||||
* - If the current value is greater than the sender's current
|
|
||||||
* power level, reject. */
|
|
||||||
CheckPLOld("events");
|
|
||||||
|
|
||||||
/* Step 10.5: For each entry being added to, or changed in, the events
|
|
||||||
* property:
|
|
||||||
* - If the new value is greater than the sender's current power level,
|
|
||||||
* reject. */
|
|
||||||
CheckPLNew("events");
|
|
||||||
|
|
||||||
/* Steps 10.6 and 10.7 are effectively the same. */
|
|
||||||
CheckPLOld("users");
|
|
||||||
CheckPLNew("users");
|
|
||||||
#undef CheckPLOld
|
|
||||||
#undef CheckPLNew
|
|
||||||
/* Step 10.8: Otherwise, allow. */
|
|
||||||
JsonFree(prev_plevent);
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
#ifndef TELODENDRIA_IROOM_V1_AUTH_H
|
|
||||||
#define TELODENDRIA_IROOM_V1_AUTH_H
|
|
||||||
|
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if a room creation PDU would be allowed
|
|
||||||
* by the auth rules in room version 1 (step 1)
|
|
||||||
*/
|
|
||||||
extern bool AuthoriseCreateV1(PduV1, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if an auth event PDU would be allowed by the
|
|
||||||
* auth rules in room version 1 (step 2)
|
|
||||||
*/
|
|
||||||
extern bool ConsiderAuthEventsV1(Room *, PduV1, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if an alias PDU would be authorised in room
|
|
||||||
* version 1 (step 4)
|
|
||||||
*/
|
|
||||||
extern bool AuthoriseAliasV1(PduV1, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if a membership PDUv1 would be allowed by the
|
|
||||||
* auth rules in room version 1 (step 5)
|
|
||||||
*/
|
|
||||||
extern bool AuthoriseMemberV1(Room *, PduV1, State *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if a PDU (power levels) would be allowed by
|
|
||||||
* the auth rules in room version 1 (step 10)
|
|
||||||
*/
|
|
||||||
extern bool AuthorisePowerLevelsV1(Room *, PduV1, State *);
|
|
||||||
#endif
|
|
|
@ -1,17 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
RoomIsSoftfailedV1(PduV1 pdu)
|
|
||||||
{
|
|
||||||
return pdu._unsigned.pdu_status == PDUV1_STATUS_SOFTFAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
RoomIsRejectedV1(PduV1 pdu)
|
|
||||||
{
|
|
||||||
return pdu._unsigned.pdu_status == PDUV1_STATUS_SOFTFAIL;
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu, ServerPart serv)
|
|
||||||
{
|
|
||||||
char *unused;
|
|
||||||
Array *prev_events;
|
|
||||||
size_t i;
|
|
||||||
CommonID cid;
|
|
||||||
if (PduV1FromJson(event, pdu, &unused))
|
|
||||||
{
|
|
||||||
/* TODO: Clean up some fields */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Consider the PDU dropped by default */
|
|
||||||
pdu->_unsigned.pdu_status = PDUV1_STATUS_DROPPED;
|
|
||||||
|
|
||||||
/* TODO: Create a PDU of our own, signed and everything.
|
|
||||||
* https://telodendria.io/blog/matrix-protocol-overview
|
|
||||||
* has some ideas on how this could be done(up until stage 5). */
|
|
||||||
pdu->sender =
|
|
||||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "sender")));
|
|
||||||
pdu->type =
|
|
||||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "type")));
|
|
||||||
pdu->redacts =
|
|
||||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "redacts")));
|
|
||||||
pdu->_unsigned.transaction_id =
|
|
||||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "transaction")));
|
|
||||||
if (JsonGet(event, 1, "state_key"))
|
|
||||||
{
|
|
||||||
pdu->state_key =
|
|
||||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "state_key")));
|
|
||||||
}
|
|
||||||
pdu->auth_events = ArrayCreate();
|
|
||||||
pdu->origin_server_ts = UtilTsMillis();
|
|
||||||
pdu->content =
|
|
||||||
JsonDuplicate(JsonValueAsObject(JsonGet(event, 1, "content")));
|
|
||||||
pdu->room_id = StrDuplicate(room->id);
|
|
||||||
pdu->signatures = HashMapCreate();
|
|
||||||
pdu->depth = RoomGetDepth(room) + 1;
|
|
||||||
pdu->depth = pdu->depth >= INT64_MAX ? INT64_MAX : pdu->depth;
|
|
||||||
|
|
||||||
/* Create a random event ID for this PDU.
|
|
||||||
* TODO: Optionally verify whenever it's already used, but that
|
|
||||||
* would be unlikely considering the lengths of event IDs. */
|
|
||||||
cid.sigil = '$';
|
|
||||||
cid.local = StrRandom(32);
|
|
||||||
cid.server.hostname = StrDuplicate(serv.hostname);
|
|
||||||
cid.server.port = serv.port ? StrDuplicate(serv.port) : NULL;
|
|
||||||
pdu->event_id = ParserRecomposeCommonID(cid);
|
|
||||||
CommonIDFree(cid);
|
|
||||||
|
|
||||||
|
|
||||||
/* Fill prev_events with actual event data.
|
|
||||||
* Note that we don't actually *clear* out these from our list, as
|
|
||||||
* that should be done later. */
|
|
||||||
pdu->prev_events = ArrayCreate();
|
|
||||||
prev_events = RoomPrevEventsGet(room);
|
|
||||||
for (i = 0; i < ArraySize(prev_events); i++)
|
|
||||||
{
|
|
||||||
HashMap *event = JsonValueAsObject(ArrayGet(prev_events, i));
|
|
||||||
JsonValue *event_id = JsonGet(event, 1, "event_id");
|
|
||||||
|
|
||||||
ArrayAdd(pdu->prev_events, JsonValueDuplicate(event_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Signatures.
|
|
||||||
* We currently *don't* have an Ed25519 implementation. */
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
|
@ -1,565 +0,0 @@
|
||||||
#include "Room/internal.h"
|
|
||||||
|
|
||||||
#include <Schema/Relation.h>
|
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
|
|
||||||
static char *
|
|
||||||
RoomHashEventV1(PduV1 pdu)
|
|
||||||
{
|
|
||||||
HashMap *json = PduV1ToJson(&pdu);
|
|
||||||
char *b64;
|
|
||||||
|
|
||||||
b64 = EventContentHash(json);
|
|
||||||
|
|
||||||
JsonFree(json);
|
|
||||||
return b64;
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
EventFits(HashMap *pdu)
|
|
||||||
{
|
|
||||||
int size = CanonicalJsonEncode(pdu, NULL);
|
|
||||||
JsonValue *key;
|
|
||||||
|
|
||||||
/* Main PDU length is 65536 bytes */
|
|
||||||
if (size > 65536)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#define VerifyKey(k,s) do \
|
|
||||||
{ \
|
|
||||||
if ((key = JsonGet(pdu, 1, k)) && \
|
|
||||||
(strlen(JsonValueAsString(key)) > s)) \
|
|
||||||
{ \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while (0)
|
|
||||||
VerifyKey("sender", 255);
|
|
||||||
VerifyKey("room_id", 255);
|
|
||||||
VerifyKey("state_key", 255);
|
|
||||||
VerifyKey("type", 255);
|
|
||||||
VerifyKey("event_id", 255);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#undef VerifyKey
|
|
||||||
}
|
|
||||||
static bool
|
|
||||||
EventFitsV1(PduV1 pdu)
|
|
||||||
{
|
|
||||||
HashMap *hm;
|
|
||||||
bool ret;
|
|
||||||
|
|
||||||
hm = PduV1ToJson(&pdu);
|
|
||||||
ret = EventFits(hm);
|
|
||||||
|
|
||||||
JsonFree(hm);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Rejection */
|
|
||||||
static PduV1Status
|
|
||||||
RoomGetEventStatusV1(Room *room, PduV1 *pdu, State *prev, bool client, char **errp)
|
|
||||||
{
|
|
||||||
if (!room || !pdu || !prev)
|
|
||||||
{
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "Illegal arguments given to RoomGetEventStatusV1";
|
|
||||||
}
|
|
||||||
return PDUV1_STATUS_DROPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EventFitsV1(*pdu))
|
|
||||||
{
|
|
||||||
/* Reject this event as it is too large. */
|
|
||||||
if (errp)
|
|
||||||
{
|
|
||||||
*errp = "PDU is too large to fit";
|
|
||||||
}
|
|
||||||
return PDUV1_STATUS_DROPPED;
|
|
||||||
}
|
|
||||||
if (!RoomAuthoriseEventV1(room, *pdu, prev, errp))
|
|
||||||
{
|
|
||||||
/* Reject this event as the PDU's own state does not allow it. */
|
|
||||||
return PDUV1_STATUS_DROPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client)
|
|
||||||
{
|
|
||||||
/* Checking for soft-failure is not that useful in that case,
|
|
||||||
* we're only doing pointless computation. */
|
|
||||||
State *current = StateCurrent(room);
|
|
||||||
|
|
||||||
if (!RoomAuthoriseEventV1(room, *pdu, current, errp))
|
|
||||||
{
|
|
||||||
StateFree(current);
|
|
||||||
return PDUV1_STATUS_SOFTFAIL;
|
|
||||||
}
|
|
||||||
StateFree(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PDUV1_STATUS_ACCEPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
RedactPDU1(HashMap *obj)
|
|
||||||
{
|
|
||||||
Array *keys;
|
|
||||||
HashMap *content;
|
|
||||||
char *type = JsonValueAsString(HashMapGet(obj, "type"));
|
|
||||||
size_t i;
|
|
||||||
if (!obj)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = HashMapKeys(obj);
|
|
||||||
for (i = 0; i < ArraySize(keys); i++)
|
|
||||||
{
|
|
||||||
char *key = ArrayGet(keys, i);
|
|
||||||
if (!StrEquals(key, "event_id") && !StrEquals(key, "type") &&
|
|
||||||
!StrEquals(key, "room_id") && !StrEquals(key, "sender") &&
|
|
||||||
!StrEquals(key, "state_key")&& !StrEquals(key, "content") &&
|
|
||||||
!StrEquals(key, "hashes") && !StrEquals(key, "signatures") &&
|
|
||||||
!StrEquals(key, "depth") && !StrEquals(key, "prev_events") &&
|
|
||||||
!StrEquals(key, "auth_events")&& !StrEquals(key, "origin") &&
|
|
||||||
!StrEquals(key, "unsigned") &&
|
|
||||||
!StrEquals(key, "origin_server_ts")&&!StrEquals(key, "membership"))
|
|
||||||
{
|
|
||||||
JsonValueFree(HashMapDelete(obj, key));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArrayFree(keys);
|
|
||||||
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
obj,
|
|
||||||
"unsigned", JsonValueObject(HashMapCreate())
|
|
||||||
));
|
|
||||||
|
|
||||||
content = JsonValueAsObject(HashMapGet(obj, "content"));
|
|
||||||
keys = HashMapKeys(content);
|
|
||||||
for (i = 0; i < ArraySize(keys); i++)
|
|
||||||
{
|
|
||||||
char *key = ArrayGet(keys, i);
|
|
||||||
if (StrEquals(type, "m.room.member"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "membership"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (StrEquals(type, "m.room.create"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "creator"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (StrEquals(type, "m.room.join_rules"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "join_rule"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (StrEquals(type, "m.room.aliases"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "aliases"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (StrEquals(type, "m.room.history_visibility"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "history_visibility"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (StrEquals(type, "m.room.power_levels"))
|
|
||||||
{
|
|
||||||
if (StrEquals(key, "ban") ||
|
|
||||||
StrEquals(key, "events") ||
|
|
||||||
StrEquals(key, "events_default") ||
|
|
||||||
StrEquals(key, "kick") ||
|
|
||||||
StrEquals(key, "redact") ||
|
|
||||||
StrEquals(key, "state_default") ||
|
|
||||||
StrEquals(key, "users") ||
|
|
||||||
StrEquals(key, "users_default"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonValueFree(HashMapDelete(content, key));
|
|
||||||
}
|
|
||||||
ArrayFree(keys);
|
|
||||||
}
|
|
||||||
bool
|
|
||||||
RoomAddEventV1(Room *room, PduV1 pdu, PduV1Status status)
|
|
||||||
{
|
|
||||||
DbRef *event_ref;
|
|
||||||
Array *prev_events = NULL, *leaves = NULL;
|
|
||||||
HashMap *leaves_json = NULL, *pdu_json = NULL;
|
|
||||||
State *prev_state = NULL;
|
|
||||||
JsonValue *leaves_val;
|
|
||||||
char *safe_id, *prev_id;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if (!room || room->version >= 3 ||
|
|
||||||
status == PDUV1_STATUS_DROPPED)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Insert our PDU into the event table, regardless of status */
|
|
||||||
safe_id = CreateSafeID(pdu.event_id);
|
|
||||||
event_ref = DbCreate(room->db, 4, "rooms", room->id, "events", safe_id);
|
|
||||||
pdu_json = PduV1ToJson(&pdu);
|
|
||||||
prev_state = StateResolve(room, pdu_json);
|
|
||||||
prev_id = StrDuplicate(StateGet(prev_state, pdu.type, pdu.state_key));
|
|
||||||
StateFree(prev_state);
|
|
||||||
|
|
||||||
/* TODO: Use those values concretely. */
|
|
||||||
pdu._unsigned.pdu_status = status;
|
|
||||||
JsonSet(DbJson(event_ref), JsonValueObject(pdu_json), 1, "pdu");
|
|
||||||
pdu_json = NULL;
|
|
||||||
JsonSet(
|
|
||||||
DbJson(event_ref),
|
|
||||||
JsonValueString(PduV1StatusToStr(status)),
|
|
||||||
1, "status"
|
|
||||||
);
|
|
||||||
JsonSet(
|
|
||||||
DbJson(event_ref),
|
|
||||||
JsonValueString(prev_id),
|
|
||||||
1, "prev_event"
|
|
||||||
);
|
|
||||||
JsonSet(
|
|
||||||
DbJson(event_ref),
|
|
||||||
JsonValueArray(ArrayCreate()),
|
|
||||||
1, "next_events"
|
|
||||||
);
|
|
||||||
JsonSet(
|
|
||||||
DbJson(event_ref),
|
|
||||||
JsonValueString(pdu._unsigned.transaction_id),
|
|
||||||
1, "transaction"
|
|
||||||
);
|
|
||||||
|
|
||||||
DbUnlock(room->db, event_ref);
|
|
||||||
Free(safe_id);
|
|
||||||
Free(prev_id);
|
|
||||||
|
|
||||||
/* Only accepted PDUs get to do the news */
|
|
||||||
if (status == PDUV1_STATUS_ACCEPTED)
|
|
||||||
{
|
|
||||||
/* Remove managed leaves here. */
|
|
||||||
leaves_json = DbJson(room->leaves_ref);
|
|
||||||
leaves_val = JsonValueDuplicate(JsonGet(leaves_json, 1, "leaves"));
|
|
||||||
leaves = JsonValueAsArray(leaves_val);
|
|
||||||
Free(leaves_val); /* We do not care about the array's JSON shell. */
|
|
||||||
|
|
||||||
prev_events = pdu.prev_events;
|
|
||||||
for (i = 0; i < ArraySize(prev_events); i++)
|
|
||||||
{
|
|
||||||
JsonValue *event_val = ArrayGet(prev_events, i);
|
|
||||||
char *event_id = JsonValueAsString(event_val);
|
|
||||||
size_t j;
|
|
||||||
ssize_t delete_index = -1;
|
|
||||||
|
|
||||||
for (j = 0; j < ArraySize(leaves); j++)
|
|
||||||
{
|
|
||||||
JsonValue *leaf_val = ArrayGet(leaves, j);
|
|
||||||
HashMap *leaf_object = JsonValueAsObject(leaf_val);
|
|
||||||
char *leaf_id =
|
|
||||||
JsonValueAsString(JsonGet(leaf_object, 1, "event_id"));
|
|
||||||
|
|
||||||
if (StrEquals(leaf_id, event_id))
|
|
||||||
{
|
|
||||||
delete_index = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (delete_index == -1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
JsonValueFree(ArrayDelete(leaves, delete_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add our current PDU to the leaves. */
|
|
||||||
ArrayAdd(leaves, JsonValueObject(PduV1ToJson(&pdu)));
|
|
||||||
leaves_json = JsonDuplicate(leaves_json);
|
|
||||||
JsonValueFree(HashMapDelete(leaves_json, "leaves"));
|
|
||||||
JsonSet(leaves_json, JsonValueArray(leaves), 1, "leaves");
|
|
||||||
DbJsonSet(room->leaves_ref, leaves_json);
|
|
||||||
JsonFree(leaves_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(prev_events); i++)
|
|
||||||
{
|
|
||||||
JsonValue *event_val = ArrayGet(prev_events, i);
|
|
||||||
char *id = JsonValueAsString(event_val);
|
|
||||||
char *error = NULL;
|
|
||||||
PduV1 prev_pdu = { 0 };
|
|
||||||
HashMap *prev_object = NULL;
|
|
||||||
Array *next_events = NULL;
|
|
||||||
HashMap *refObj = NULL;
|
|
||||||
HashMap *pduObj = NULL;
|
|
||||||
|
|
||||||
/* TODO: This confuses me! */
|
|
||||||
event_ref = DbLock(room->db, 4, "rooms", room->id, "events", id);
|
|
||||||
refObj = DbJson(event_ref);
|
|
||||||
pduObj = JsonValueAsObject(HashMapGet(refObj, "pdu"));
|
|
||||||
PduV1FromJson(pduObj, &prev_pdu, &error);
|
|
||||||
|
|
||||||
next_events = JsonValueAsArray(HashMapGet(refObj, "next_events"));
|
|
||||||
ArrayAdd(next_events, JsonValueString(pdu.event_id));
|
|
||||||
|
|
||||||
prev_object = PduV1ToJson(&prev_pdu);
|
|
||||||
JsonValueFree(HashMapSet(refObj, "pdu", JsonValueObject(prev_object)));
|
|
||||||
PduV1Free(&prev_pdu);
|
|
||||||
DbUnlock(room->db, event_ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Accepted PDUs should be the only one that users should be
|
|
||||||
* notified about. */
|
|
||||||
if (status == PDUV1_STATUS_ACCEPTED)
|
|
||||||
{
|
|
||||||
HashMap *relates_to =
|
|
||||||
JsonValueAsObject(HashMapGet(pdu.content, "m.relates_to"));
|
|
||||||
Relation rel = { 0 };
|
|
||||||
State *state;
|
|
||||||
char *type, *state_key, *event_id, *errp = NULL;
|
|
||||||
|
|
||||||
pdu_json = PduV1ToJson(&pdu);
|
|
||||||
|
|
||||||
/* If we have a membership change, then add it to the
|
|
||||||
* proper table. */
|
|
||||||
if (relates_to && RelationFromJson(relates_to, &rel, &errp))
|
|
||||||
{
|
|
||||||
DbRef *relate = DbLock(
|
|
||||||
room->db,
|
|
||||||
4, "rooms", room->id, "events", rel.event_id
|
|
||||||
);
|
|
||||||
HashMap *relObj = DbJson(relate);
|
|
||||||
if (relObj)
|
|
||||||
{
|
|
||||||
Array *relList = JsonValueAsArray(HashMapGet(
|
|
||||||
relObj, "relations"
|
|
||||||
));
|
|
||||||
if (!relList)
|
|
||||||
{
|
|
||||||
relList = ArrayCreate();
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
relObj, "relations", JsonValueArray(relList)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: We may want to treat the relation as 'special'
|
|
||||||
* if the specification asks us to bundle it. */
|
|
||||||
ArrayAdd(relList, JsonValueString(pdu.event_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
RelationFree(&rel);
|
|
||||||
DbUnlock(room->db, relate);
|
|
||||||
}
|
|
||||||
if (StrEquals(pdu.type, "m.room.member"))
|
|
||||||
{
|
|
||||||
CommonID *id = UserIdParse(pdu.state_key, NULL);
|
|
||||||
User *user = UserLockID(room->db, id);
|
|
||||||
char *membership = JsonValueAsString(
|
|
||||||
HashMapGet(pdu.content, "membership")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (StrEquals(membership, "join") && user)
|
|
||||||
{
|
|
||||||
UserAddJoin(user, room->id);
|
|
||||||
UserPushJoinSync(user, room->id);
|
|
||||||
}
|
|
||||||
else if (StrEquals(membership, "invite") && user)
|
|
||||||
{
|
|
||||||
UserAddInvite(user, room->id);
|
|
||||||
UserPushInviteSync(user, room->id);
|
|
||||||
}
|
|
||||||
else if ((StrEquals(membership, "leave") && user) ||
|
|
||||||
StrEquals(membership, "ban"))
|
|
||||||
{
|
|
||||||
UserRemoveInvite(user, room->id);
|
|
||||||
UserRemoveJoin(user, room->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
}
|
|
||||||
else if (StrEquals(pdu.type, "m.room.redaction") && pdu.redacts)
|
|
||||||
{
|
|
||||||
char *redacted = pdu.redacts;
|
|
||||||
DbRef *eventRef = DbLock(room->db,
|
|
||||||
4, "rooms", room->id,
|
|
||||||
"events", redacted
|
|
||||||
);
|
|
||||||
|
|
||||||
RedactPDU1(
|
|
||||||
JsonValueAsObject(HashMapGet(DbJson(eventRef), "pdu"))
|
|
||||||
);
|
|
||||||
JsonValueFree(HashMapSet(
|
|
||||||
DbJson(eventRef),
|
|
||||||
"redacted_by", JsonValueString(pdu.event_id)
|
|
||||||
));
|
|
||||||
DbUnlock(room->db, eventRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = StateCurrent(room);
|
|
||||||
while (StateIterate(state, &type, &state_key, (void **) &event_id))
|
|
||||||
{
|
|
||||||
if (StrEquals(type, "m.room.member"))
|
|
||||||
{
|
|
||||||
CommonID *id = UserIdParse(state_key, NULL);
|
|
||||||
User *user = UserLockID(room->db, id);
|
|
||||||
|
|
||||||
UserPushEvent(user, pdu_json);
|
|
||||||
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
}
|
|
||||||
Free(type);
|
|
||||||
Free(state_key);
|
|
||||||
}
|
|
||||||
StateFree(state);
|
|
||||||
JsonFree(pdu_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
HashMap *
|
|
||||||
RoomEventSendV1(Room * room, HashMap * event, char **errp)
|
|
||||||
{
|
|
||||||
PduV1 pdu = { 0 };
|
|
||||||
HashMap *pdu_object = NULL;
|
|
||||||
bool client_event, valid = false;
|
|
||||||
State *state = NULL;
|
|
||||||
PduV1Status status;
|
|
||||||
|
|
||||||
client_event = !PopulateEventV1(room, event, &pdu, RoomGetCreator(room));
|
|
||||||
pdu_object = PduV1ToJson(&pdu);
|
|
||||||
|
|
||||||
state = StateResolve(room, pdu_object);
|
|
||||||
|
|
||||||
if (client_event)
|
|
||||||
{
|
|
||||||
char *ev_id;
|
|
||||||
#define AddState(type, key) do \
|
|
||||||
{ \
|
|
||||||
ev_id = StateGet(state, type, key); \
|
|
||||||
if (ev_id) \
|
|
||||||
{ \
|
|
||||||
JsonValue *v = JsonValueString(ev_id); \
|
|
||||||
ArrayAdd(pdu.auth_events, v); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while (0)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Implemented from
|
|
||||||
* https://spec.matrix.org/v1.7/server-server-api/
|
|
||||||
* #auth-events-selection */
|
|
||||||
AddState("m.room.create", "");
|
|
||||||
AddState("m.room.power_levels", "");
|
|
||||||
AddState("m.room.member", pdu.sender);
|
|
||||||
if (StrEquals(pdu.type, "m.room.member"))
|
|
||||||
{
|
|
||||||
char *target = pdu.state_key;
|
|
||||||
char *membership =
|
|
||||||
JsonValueAsString(JsonGet(pdu.content, 1, "membership"));
|
|
||||||
char *auth_via =
|
|
||||||
JsonValueAsString(
|
|
||||||
JsonGet(
|
|
||||||
pdu.content, 1, "join_authorised_via_users_server")
|
|
||||||
);
|
|
||||||
HashMap *inv =
|
|
||||||
JsonValueAsObject(
|
|
||||||
JsonGet(pdu.content, 1, "third_party_invite"));
|
|
||||||
|
|
||||||
if (target && !StrEquals(target, pdu.sender))
|
|
||||||
{
|
|
||||||
AddState("m.room.member", target);
|
|
||||||
}
|
|
||||||
if (StrEquals(membership, "join") ||
|
|
||||||
StrEquals(membership, "invite"))
|
|
||||||
{
|
|
||||||
AddState("m.room.join_rules", "");
|
|
||||||
}
|
|
||||||
if (StrEquals(membership, "invite") && inv)
|
|
||||||
{
|
|
||||||
char *token =
|
|
||||||
JsonValueAsString(JsonGet(inv, 2, "signed", "token"));
|
|
||||||
AddState("m.room.third_party_invite", token);
|
|
||||||
}
|
|
||||||
if (auth_via && room->version >= 8)
|
|
||||||
{
|
|
||||||
AddState("m.room.member", auth_via);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pdu.hashes.sha256 = RoomHashEventV1(pdu);
|
|
||||||
#undef AddState
|
|
||||||
}
|
|
||||||
/* It seems like we need to behave differently in terms of
|
|
||||||
* verifying PDUs from the client/federation.
|
|
||||||
* - In the client, we just do not care about any events that
|
|
||||||
* are incorrect. We simply drop them, as if they never existed.
|
|
||||||
* - In the server on the otherhand, the only place where we can
|
|
||||||
* possibly drop events as such is if it fails signatures. In
|
|
||||||
* other cases, we *have* to store it(ableit with flags, to
|
|
||||||
* restrict what we can do).
|
|
||||||
* - Rejection: We avoid relaying/linking those to anything.
|
|
||||||
* They must NOT be used for stateres.
|
|
||||||
* - Softfail: Essentially almost the same as rejects, except
|
|
||||||
* that they *are* used for stateres.
|
|
||||||
* I guess a way to do this may be to add a CheckAuthStatus
|
|
||||||
* function that also verifies if it is a client event, and returns
|
|
||||||
* an enum:
|
|
||||||
* - DROPPED: Do NOT process it _at all_
|
|
||||||
* - REJECT: Process that event as if it was rejected
|
|
||||||
* - SOFTFAIL: Process the event as if it was softfailed
|
|
||||||
* The main issue is storing it in the PDU. A naive approach would be to
|
|
||||||
* add the status to the unsigned field of the PDU, and add functions to
|
|
||||||
* return the status. I guess that is possible, but then again, can we
|
|
||||||
* really abuse the unsigned field for this?
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* TODO: For PDU events, we should verify their hashes. */
|
|
||||||
status = RoomGetEventStatusV1(room, &pdu, state, client_event, errp);
|
|
||||||
if (status == PDUV1_STATUS_DROPPED)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
StateFree(state);
|
|
||||||
|
|
||||||
RoomAddEventV1(room, pdu, status);
|
|
||||||
state = NULL;
|
|
||||||
valid = true;
|
|
||||||
|
|
||||||
/* If it is a client event, we should make sure that we shout at
|
|
||||||
* every other homeserver about our new event. */
|
|
||||||
|
|
||||||
finish:
|
|
||||||
if (state)
|
|
||||||
{
|
|
||||||
StateFree(state);
|
|
||||||
}
|
|
||||||
if (pdu_object)
|
|
||||||
{
|
|
||||||
JsonFree(pdu_object);
|
|
||||||
pdu_object = NULL;
|
|
||||||
}
|
|
||||||
if (valid)
|
|
||||||
{
|
|
||||||
pdu_object = PduV1ToJson(&pdu);
|
|
||||||
}
|
|
||||||
PduV1Free(&pdu);
|
|
||||||
return pdu_object;
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
#ifndef TELODENDRIA_IROOM_H
|
|
||||||
#define TELODENDRIA_IROOM_H
|
|
||||||
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Util.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
#include <Cytoplasm/Db.h>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <CanonicalJson.h>
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <Config.h>
|
|
||||||
#include <State.h>
|
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
#define IsState(p, typ, key) (StrEquals((p)->type, typ) && \
|
|
||||||
StrEquals((p)->state_key, key))
|
|
||||||
struct Room
|
|
||||||
{
|
|
||||||
Db *db;
|
|
||||||
|
|
||||||
DbRef *state_ref;
|
|
||||||
DbRef *leaves_ref; /* Reference to the leaf list */
|
|
||||||
|
|
||||||
ServerPart creator;
|
|
||||||
|
|
||||||
char *id;
|
|
||||||
int version;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates a room with the events required for its creation.
|
|
||||||
*/
|
|
||||||
extern void RoomPopulate(Room *, User *, RoomCreateRequest *, ServerPart);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if the current room state has a joinrule set to a
|
|
||||||
* specific value.
|
|
||||||
*/
|
|
||||||
extern bool RoomIsJoinRule(Room *, State *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to parse a PL value from a JsonValue, with a default
|
|
||||||
* powerlevel, if it cannot parse it.
|
|
||||||
*/
|
|
||||||
extern int64_t ParsePL(JsonValue *, int64_t);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the lowest powerlevel required to execute an action
|
|
||||||
* in a room.
|
|
||||||
*/
|
|
||||||
extern int64_t RoomMinPL(Room *, State *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the user's powerlevel at a specific state.
|
|
||||||
*/
|
|
||||||
extern int64_t RoomUserPL(Room *, State *, char *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates an event to a valid PDUv1(and returns true if
|
|
||||||
* properly created).
|
|
||||||
*/
|
|
||||||
extern bool PopulateEventV1(Room *, HashMap *, PduV1 *, ServerPart);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an event to a room, be it a PDUv1/client event
|
|
||||||
*/
|
|
||||||
extern HashMap * RoomEventSendV1(Room *, HashMap *, char **);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if the user has a specific membership at a given state.
|
|
||||||
*/
|
|
||||||
extern bool RoomUserHasMembership(Room *, State *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes a PDU's contenthash.
|
|
||||||
*/
|
|
||||||
extern char * EventContentHash(HashMap *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new "DB-safe" ID for events.
|
|
||||||
*/
|
|
||||||
extern char * CreateSafeID(char *);
|
|
||||||
|
|
||||||
#endif
|
|
24
src/Routes.c
24
src/Routes.c
|
@ -54,7 +54,6 @@ RouterBuild(void)
|
||||||
R("/_matrix/client/v3/auth/(.*)/fallback/web", RouteUiaFallback);
|
R("/_matrix/client/v3/auth/(.*)/fallback/web", RouteUiaFallback);
|
||||||
|
|
||||||
R("/_matrix/client/v3/capabilities", RouteCapabilities);
|
R("/_matrix/client/v3/capabilities", RouteCapabilities);
|
||||||
R("/_matrix/client/v3/devices", RouteDevices);
|
|
||||||
R("/_matrix/client/v3/login", RouteLogin);
|
R("/_matrix/client/v3/login", RouteLogin);
|
||||||
R("/_matrix/client/v3/logout", RouteLogout);
|
R("/_matrix/client/v3/logout", RouteLogout);
|
||||||
R("/_matrix/client/v3/logout/(all)", RouteLogout);
|
R("/_matrix/client/v3/logout/(all)", RouteLogout);
|
||||||
|
@ -77,35 +76,12 @@ RouterBuild(void)
|
||||||
|
|
||||||
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
|
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
|
||||||
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
|
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
|
||||||
R("/_matrix/client/v3/user/(.*)/account_data/(.*)", RouteLocalData);
|
|
||||||
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/send/(.*)/(.*)", RouteSendEvent);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/redact/(.*)/(.*)", RouteRedact);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/state/(.*)/(.*)", RouteSendState);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/state/(.*)", RouteSendState);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/event/(.*)", RouteFetchEvent);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/(join|leave)", RouteJoinRoom);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/(kick|ban|unban)", RouteKickRoom);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/messages", RouteMessages);
|
|
||||||
R("/_matrix/client/v3/rooms/(.*)/members", RouteMembers);
|
|
||||||
|
|
||||||
R("/_matrix/client/v3/join/(.*)", RouteJoinRoomAlias);
|
|
||||||
|
|
||||||
R("/_matrix/client/v3/joined_rooms", RouteJoinedRooms);
|
|
||||||
|
|
||||||
R("/_matrix/client/v3/createRoom", RouteCreateRoom);
|
R("/_matrix/client/v3/createRoom", RouteCreateRoom);
|
||||||
|
|
||||||
R("/_matrix/client/v3/sync", RouteSync);
|
|
||||||
|
|
||||||
R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory);
|
R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory);
|
||||||
R("/_matrix/client/v3/rooms/(.*)/aliases", RouteRoomAliases);
|
R("/_matrix/client/v3/rooms/(.*)/aliases", RouteRoomAliases);
|
||||||
|
|
||||||
/* Spoofed endpoints, to be TODO'd */
|
|
||||||
R("/_matrix/client/v3/keys/(query|upload)", RouteKeyQuery);
|
|
||||||
R("/_matrix/client/v3/pushrules", RoutePushrules);
|
|
||||||
R("/_matrix/federation/v1/hierarchy/(.*)", RouteHierarchy);
|
|
||||||
|
|
||||||
|
|
||||||
/* Telodendria Admin API Routes */
|
/* Telodendria Admin API Routes */
|
||||||
|
|
||||||
R("/_telodendria/admin/v1/(restart|shutdown|stats)", RouteProcControl);
|
R("/_telodendria/admin/v1/(restart|shutdown|stats)", RouteProcControl);
|
||||||
|
|
|
@ -1,338 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteJoinRoom, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *action = ArrayGet(path, 1);
|
|
||||||
char *sender = NULL, *serverName = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
err = "Room ID does not exist.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (StrEquals(action, "join"))
|
|
||||||
{
|
|
||||||
if (RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is already in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (!RoomCanJoin(room, sender))
|
|
||||||
{
|
|
||||||
err = "User cannot be in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
/* TODO: Custom reason parameter. */
|
|
||||||
if (!RoomJoin(room, user, &err))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_BAD_STATE, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
JsonSet(response, JsonValueString(roomId), 1, "room_id");
|
|
||||||
}
|
|
||||||
else if (StrEquals(action, "leave"))
|
|
||||||
{
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not already in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!RoomLeave(room, user, &err))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_BAD_STATE, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
}
|
|
||||||
|
|
||||||
finish:
|
|
||||||
UserIdFree(id);
|
|
||||||
JsonFree(request);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
RoomUnlock(room);
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteKickRoom, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
HashMap *content, *membership;
|
|
||||||
HashMap *pduResponse;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *action = ArrayGet(path, 1);
|
|
||||||
char *kicked = NULL, *reason = NULL;
|
|
||||||
char *sender = NULL, *serverName = NULL;
|
|
||||||
char *membershipState;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId || !action)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StrEquals(action, "kick"))
|
|
||||||
{
|
|
||||||
membershipState = "leave";
|
|
||||||
}
|
|
||||||
else if (StrEquals(action, "ban"))
|
|
||||||
{
|
|
||||||
membershipState = "ban";
|
|
||||||
}
|
|
||||||
else if (StrEquals(action, "unban"))
|
|
||||||
{
|
|
||||||
membershipState = "leave";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
kicked = JsonValueAsString(HashMapGet(request, "reason"));
|
|
||||||
if (!(kicked = JsonValueAsString(HashMapGet(request, "user_id"))))
|
|
||||||
{
|
|
||||||
err = "'user_id' field required(string), but not given";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "Sender is not already in the room";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (RoomContainsUser(room, kicked) == StrEquals(action, "unban"))
|
|
||||||
{
|
|
||||||
err = "Victim is not present in the room";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_STATE, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
content = HashMapCreate();
|
|
||||||
membership = RoomEventCreate(
|
|
||||||
sender,
|
|
||||||
"m.room.member", kicked,
|
|
||||||
content, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
HashMapSet(content, "membership", JsonValueString(membershipState));
|
|
||||||
if (reason)
|
|
||||||
{
|
|
||||||
HashMapSet(content, "reason", JsonValueString(reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
pduResponse = RoomEventSend(room, membership, &err);
|
|
||||||
if (!pduResponse)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_BAD_STATE, err);
|
|
||||||
|
|
||||||
JsonFree(membership);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
JsonFree(pduResponse);
|
|
||||||
JsonFree(membership);
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
finish:
|
|
||||||
UserIdFree(id);
|
|
||||||
JsonFree(request);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
RoomUnlock(room);
|
|
||||||
UserUnlock(user);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -84,12 +84,12 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
val = JsonGet(aliases, 2, "aliases", alias);
|
val = JsonGet(aliases, 2, "alias", alias);
|
||||||
if (val)
|
if (val)
|
||||||
{
|
{
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
HashMapSet(response, "room_id", JsonValueDuplicate(HashMapGet(JsonValueAsObject(val), "id")));
|
HashMapSet(response, "room_id", JsonValueDuplicate(HashMapGet(JsonValueAsObject(val), "id")));
|
||||||
HashMapSet(response, "servers", JsonValueDuplicate(JsonGet(aliases, 3, "aliases", alias, "servers")));
|
HashMapSet(response, "servers", JsonValueDuplicate(JsonGet(aliases, 3, "alias", alias, "servers")));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -51,7 +51,7 @@ ROUTE_IMPL(RouteCapabilities, path, argp)
|
||||||
|
|
||||||
/* TODO: When more room versions are implemented, add them to
|
/* TODO: When more room versions are implemented, add them to
|
||||||
* roomVersions */
|
* roomVersions */
|
||||||
HashMapSet(roomVersions, "1", JsonValueString("stable"));
|
HashMapSet(roomVersions, "1", JsonValueString("unstable"));
|
||||||
|
|
||||||
JsonSet(capabilities, JsonValueString("1"), 2, "m.room_versions", "default");
|
JsonSet(capabilities, JsonValueString("1"), 2, "m.room_versions", "default");
|
||||||
JsonSet(capabilities, JsonValueObject(roomVersions), 2, "m.room_versions", "available");
|
JsonSet(capabilities, JsonValueObject(roomVersions), 2, "m.room_versions", "available");
|
||||||
|
|
|
@ -23,14 +23,11 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Cytoplasm/Http.h"
|
|
||||||
#include <Routes.h>
|
#include <Routes.h>
|
||||||
|
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Log.h>
|
|
||||||
|
|
||||||
#include <Room.h>
|
#include <Schema/RoomCreateRequest.h>
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteCreateRoom, path, argp)
|
ROUTE_IMPL(RouteCreateRoom, path, argp)
|
||||||
{
|
{
|
||||||
|
@ -38,21 +35,11 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
|
||||||
|
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
Room *room = NULL;
|
RoomCreateRequest parsed;
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
RoomCreateRequest parsed = { 0 };
|
|
||||||
User *user = NULL;
|
|
||||||
Config cfg;
|
|
||||||
ServerPart server;
|
|
||||||
|
|
||||||
char *token;
|
|
||||||
char *err;
|
char *err;
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
ConfigLock(db, &cfg);
|
|
||||||
ParseServerPart(cfg.serverName, &server);
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
err = "Unknown request method.";
|
err = "Unknown request method.";
|
||||||
|
@ -61,18 +48,6 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
|
@ -80,55 +55,21 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* j2s hack: Setting to a default value like that.
|
|
||||||
* It's definitely NOT good(you can't rely on enums to
|
|
||||||
* store values outside their limits in the C standard,
|
|
||||||
* and this line will need to be changed everytime the
|
|
||||||
* preset list is changed).
|
|
||||||
*
|
|
||||||
* I do believe a decent solution to this would be to
|
|
||||||
* add a 'default' type(maybe initialised to 0 so that
|
|
||||||
* memsets would work as intended), that *wouldn't* be
|
|
||||||
* returned by j2s itself.
|
|
||||||
* TODO. */
|
|
||||||
parsed.preset = ROOM_CREATE_PRIVATE + 1;
|
|
||||||
if (!RoomCreateRequestFromJson(request, &parsed, &err))
|
if (!RoomCreateRequestFromJson(request, &parsed, &err))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, err);
|
response = MatrixErrorCreate(M_BAD_JSON, err);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
if (parsed.room_id && !(UserGetPrivileges(user) & USER_ALIAS))
|
|
||||||
{
|
|
||||||
err = "Custom room ID used without ALIAS privileges.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNAUTHORIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No longer need this now that it is parsed */
|
/* No longer need this now that it is parsed */
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
request = NULL;
|
request = NULL;
|
||||||
|
|
||||||
if (!(room = RoomCreate(db, user, &parsed, server)))
|
|
||||||
{
|
|
||||||
err = "Couldn't create room.";
|
|
||||||
/* Consider another error status. */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
JsonSet(response, JsonValueString(RoomIdGet(room)), 1, "room_id");
|
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
RoomUnlock(room);
|
|
||||||
UserUnlock(user);
|
|
||||||
ConfigUnlock(&cfg);
|
|
||||||
ServerPartFree(server);
|
|
||||||
RoomCreateRequestFree(&parsed);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteDevices, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
HashMap *devices;
|
|
||||||
|
|
||||||
char *tokenstr, *deviceID;
|
|
||||||
JsonValue *deviceVal;
|
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
Array *responseDevices;
|
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
User *user;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
msg = "This route only accepts GET.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &tokenstr);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, tokenstr);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
}
|
|
||||||
if (!(devices = UserGetDevices(user)))
|
|
||||||
{
|
|
||||||
msg = "Couldn't get user's devices. This is not right.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(response,
|
|
||||||
"devices", JsonValueArray((responseDevices = ArrayCreate()))
|
|
||||||
);
|
|
||||||
i = 0;
|
|
||||||
while (HashMapIterateReentrant(devices, &deviceID, (void **) &deviceVal, &i))
|
|
||||||
{
|
|
||||||
HashMap *deviceInfo = HashMapCreate();
|
|
||||||
HashMap *deviceObj = JsonValueAsObject(deviceVal);
|
|
||||||
|
|
||||||
JsonValue *potentialDisplay = HashMapGet(deviceObj, "displayName");
|
|
||||||
|
|
||||||
HashMapSet(deviceInfo, "device_id", JsonValueString(deviceID));
|
|
||||||
HashMapSet(deviceInfo, "display_name", JsonValueDuplicate(potentialDisplay));
|
|
||||||
/* TODO: As of now, Telodendria does not store enough information to figure out
|
|
||||||
* last timestamp and IP address. */
|
|
||||||
|
|
||||||
ArrayAdd(responseDevices, JsonValueObject(deviceInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We do not need to free the device object as it lives with the user */
|
|
||||||
finish:
|
|
||||||
UserUnlock(user);
|
|
||||||
(void) path;
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteFetchEvent, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *event = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *eventId= ArrayGet(path, 1);
|
|
||||||
char *sender = NULL, *serverName = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId || !eventId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
/* TODO: Better auth(as in check m.room.history_visibility) */
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
event = RoomEventFetch(room, eventId, true);
|
|
||||||
|
|
||||||
response = RoomEventClientify(event);
|
|
||||||
JsonFree(event);
|
|
||||||
finish:
|
|
||||||
UserIdFree(id);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
RoomUnlock(room);
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -30,13 +30,31 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
|
#include <User.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
|
static char *
|
||||||
|
GetServerName(Db * db)
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
ConfigLock(db, &config);
|
||||||
|
if (!config.ok)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = StrDuplicate(config.serverName);
|
||||||
|
|
||||||
|
ConfigUnlock(&config);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
ROUTE_IMPL(RouteFilter, path, argp)
|
ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -62,7 +80,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
serverName = GetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
@ -171,7 +189,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
DbJsonSet(ref, filterJson);
|
DbJsonSet(ref, filterJson);
|
||||||
DbUnlock(db, ref);
|
DbUnlock(db, ref);
|
||||||
|
|
||||||
JsonFree(filterJson);
|
Free(filterJson);
|
||||||
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
HashMapSet(response, "filter_id", JsonValueString(filterId));
|
HashMapSet(response, "filter_id", JsonValueString(filterId));
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Cytoplasm/HttpServer.h>
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteHierarchy, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(response, "children", JsonValueArray(ArrayCreate()));
|
|
||||||
HashMapSet(response, "inaccessible_children", JsonValueArray(ArrayCreate()));
|
|
||||||
HashMapSet(response, "room", JsonValueObject(HashMapCreate()));
|
|
||||||
(void) path;
|
|
||||||
finish:
|
|
||||||
JsonFree(request);
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteJoinRoomAlias, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *sender = NULL, *serverName = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (*roomId != '!')
|
|
||||||
{
|
|
||||||
roomId = RoomResolveAlias(db, roomId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
roomId = StrDuplicate(roomId);
|
|
||||||
}
|
|
||||||
if (!roomId)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
err = "Room ID does not exist.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is already in the room.";
|
|
||||||
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
if (!RoomCanJoin(room, sender))
|
|
||||||
{
|
|
||||||
err = "User cannot be in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
/* TODO: Custom reason parameter. */
|
|
||||||
if (!RoomJoin(room, user, &err))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
JsonSet(response, JsonValueString(roomId), 1, "room_id");
|
|
||||||
|
|
||||||
finish:
|
|
||||||
Free(roomId);
|
|
||||||
UserIdFree(id);
|
|
||||||
JsonFree(request);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
RoomUnlock(room);
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteJoinedRooms, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
Array *rawRoomList;
|
|
||||||
Array *roomIds;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
rawRoomList = UserListJoins(user);
|
|
||||||
roomIds = ArrayCreate();
|
|
||||||
for (i = 0; i < ArraySize(rawRoomList); i++)
|
|
||||||
{
|
|
||||||
ArrayAdd(roomIds, JsonValueString(ArrayGet(rawRoomList, i)));
|
|
||||||
}
|
|
||||||
UserFreeList(rawRoomList);
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(response, "joined_rooms", JsonValueArray(roomIds));
|
|
||||||
|
|
||||||
(void) path;
|
|
||||||
finish:
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Cytoplasm/HttpServer.h>
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Schema/KeyUpload.h>
|
|
||||||
|
|
||||||
HashMap *
|
|
||||||
UploadKey(RouteArgs *args, User *user, KeyUploadRequest *req, char *sender)
|
|
||||||
{
|
|
||||||
char *deviceId = UserGetDeviceId(user);
|
|
||||||
KeyResponse response = { 0 };
|
|
||||||
HashMap *json;
|
|
||||||
char *fbKey;
|
|
||||||
JsonValue *fbValue;
|
|
||||||
size_t i;
|
|
||||||
if (!user || !req || !sender)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
Log(LOG_ERR, "did=%s", deviceId);
|
|
||||||
if (req->device_keys.user_id)
|
|
||||||
{
|
|
||||||
HashMap *publicKeys;
|
|
||||||
char *pkTag, *pk;
|
|
||||||
/* We have device key information */
|
|
||||||
if (!StrEquals(req->device_keys.user_id, sender))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
return MatrixErrorCreate(
|
|
||||||
M_UNAUTHORIZED, "Device key update has an invalid user ID"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!StrEquals(req->device_keys.device_id, deviceId))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
return MatrixErrorCreate(
|
|
||||||
M_UNAUTHORIZED, "Device key update has an invalid device ID"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check the public key list */
|
|
||||||
publicKeys = req->device_keys.keys;
|
|
||||||
i = 0;
|
|
||||||
while (HashMapIterateReentrant(publicKeys, &pkTag, (void **) &pk, &i))
|
|
||||||
{
|
|
||||||
char *pktDID = strchr(pkTag, ':');
|
|
||||||
|
|
||||||
/* Maybe C does need NULL saturation */
|
|
||||||
pktDID = pktDID ? pktDID + 1 : NULL;
|
|
||||||
if (!StrEquals(pktDID, deviceId))
|
|
||||||
{
|
|
||||||
/* As far as I know, we're not meant to handle other devices'
|
|
||||||
* public keys */
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
Log(LOG_ERR, "%s!=%s 1", pktDID, deviceId);
|
|
||||||
return MatrixErrorCreate(
|
|
||||||
M_UNAUTHORIZED, "Device key update has an invalid device ID"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSetDeviceKeys(user, &req->device_keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserClearFallbackKeys(user);
|
|
||||||
i = 0;
|
|
||||||
while (HashMapIterateReentrant(req->fallback_keys, &fbKey, (void **) &fbValue, &i))
|
|
||||||
{
|
|
||||||
char *fbKID = strchr(fbKey, ':');
|
|
||||||
size_t len = fbKID ? fbKID - fbKey : 0;
|
|
||||||
char algo[len + 1];
|
|
||||||
|
|
||||||
memcpy(algo, fbKey, len);
|
|
||||||
algo[len] = '\0';
|
|
||||||
|
|
||||||
/* Maybe C does need NULL saturation */
|
|
||||||
fbKID = fbKID ? fbKID + 1 : NULL;
|
|
||||||
|
|
||||||
UserAddKey(user, fbKey, fbValue, true);
|
|
||||||
(void) fbKID;
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
while (HashMapIterateReentrant(req->one_time_keys, &fbKey, (void **) &fbValue, &i))
|
|
||||||
{
|
|
||||||
char *fbKID = strchr(fbKey, ':');
|
|
||||||
size_t len = fbKID ? fbKID - fbKey : 0;
|
|
||||||
char algo[len + 1];
|
|
||||||
|
|
||||||
memcpy(algo, fbKey, len);
|
|
||||||
algo[len] = '\0';
|
|
||||||
|
|
||||||
/* Maybe C does need NULL saturation */
|
|
||||||
fbKID = fbKID ? fbKID + 1 : NULL;
|
|
||||||
|
|
||||||
UserAddKey(user, fbKey, fbValue, false);
|
|
||||||
(void) fbKID;
|
|
||||||
}
|
|
||||||
response.one_time_key_counts = UserGetOnetimeCounts(user);
|
|
||||||
UserNotifyUser(UserGetName(user));
|
|
||||||
json = KeyResponseToJson(&response);
|
|
||||||
KeyResponseFree(&response);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteKeyQuery, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
CommonID *id = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
User *user = NULL;
|
|
||||||
|
|
||||||
char *serverName = NULL;
|
|
||||||
char *sender = NULL;
|
|
||||||
|
|
||||||
char *method = ArrayGet(path, 0);
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StrEquals(method, "upload"))
|
|
||||||
{
|
|
||||||
KeyUploadRequest upload = { 0 };
|
|
||||||
|
|
||||||
if (!KeyUploadRequestFromJson(request, &upload, &err))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((response = UploadKey(args, user, &upload, sender)))
|
|
||||||
{
|
|
||||||
KeyUploadRequestFree(&upload);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
KeyUploadRequestFree(&upload);
|
|
||||||
}
|
|
||||||
else if (StrEquals(method, "query"))
|
|
||||||
{
|
|
||||||
/* TODO: Fetch a user's key information */
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
(void) path;
|
|
||||||
finish:
|
|
||||||
JsonFree(request);
|
|
||||||
UserUnlock(user);
|
|
||||||
|
|
||||||
Free(serverName);
|
|
||||||
UserIdFree(id);
|
|
||||||
Free(sender);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteLocalData, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *serverName = NULL;
|
|
||||||
|
|
||||||
char *userParam = ArrayGet(path, 0);
|
|
||||||
char *dataKey = ArrayGet(path, 1);
|
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!userParam || !dataKey)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
id = UserIdParse(userParam, serverName);
|
|
||||||
if (!id)
|
|
||||||
{
|
|
||||||
msg = "Invalid user ID.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ParserServerNameEquals(id->server, serverName))
|
|
||||||
{
|
|
||||||
msg = "Cannot use /filter for non-local users.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNAUTHORIZED, msg);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!StrEquals(id->local, UserGetName(user)))
|
|
||||||
{
|
|
||||||
msg = "Unauthorized to use /account_data.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
|
||||||
{
|
|
||||||
case HTTP_GET:
|
|
||||||
{
|
|
||||||
HashMap *accountData = UserGetAccountData(user, dataKey);
|
|
||||||
if (!accountData)
|
|
||||||
{
|
|
||||||
msg = "Couldn't find account data.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
|
||||||
goto finish;
|
|
||||||
|
|
||||||
}
|
|
||||||
response = accountData;
|
|
||||||
}; break;
|
|
||||||
case HTTP_PUT:
|
|
||||||
{
|
|
||||||
if (StrEquals(dataKey, "m.fully_read"))
|
|
||||||
{
|
|
||||||
msg = "Cannot use protected data key.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
UserSetAccountData(user, dataKey, request);
|
|
||||||
response = HashMapCreate();
|
|
||||||
}; break;
|
|
||||||
default:
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
finish:
|
|
||||||
Free(serverName);
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
JsonFree(request);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
static bool
|
|
||||||
IsAllowed(HashMap *e, HashMap *params)
|
|
||||||
{
|
|
||||||
char *not_membership = HashMapGet(params, "not_membership");
|
|
||||||
char *membership = HashMapGet(params, "membership");
|
|
||||||
char *e_membership = JsonValueAsString(JsonGet(e, 2, "content", "membership"));
|
|
||||||
|
|
||||||
return StrEquals(e_membership, membership) || !StrEquals(e_membership, not_membership);
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteMembers, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *params = HttpRequestParams(args->context);
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *entryType, *entryKey, *entry;
|
|
||||||
char *serverName = NULL, *sender = NULL;
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *at = HashMapGet(params, "at");
|
|
||||||
char *atId = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
|
|
||||||
Array *messages = NULL, *batch;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
State *state = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
messages = UserGetEvents(user, at, roomId);
|
|
||||||
atId = ArrayGet(messages, 0);
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!atId)
|
|
||||||
{
|
|
||||||
state = StateCurrent(room);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = RoomStateGetID(room, atId);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
batch = ArrayCreate();
|
|
||||||
HashMapSet(response, "batch", JsonValueArray(batch));
|
|
||||||
while (StateIterate(state, &entryType, &entryKey, (void **) &entry))
|
|
||||||
{
|
|
||||||
HashMap *event;
|
|
||||||
if (!StrEquals(entryType, "m.room.member"))
|
|
||||||
{
|
|
||||||
Free(entryType);
|
|
||||||
Free(entryKey);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsAllowed((event = RoomEventFetch(room, entry, true)), params))
|
|
||||||
{
|
|
||||||
ArrayAdd(batch, JsonValueObject(event));
|
|
||||||
event = NULL;
|
|
||||||
}
|
|
||||||
Free(entryType);
|
|
||||||
Free(entryKey);
|
|
||||||
JsonFree(event);
|
|
||||||
}
|
|
||||||
StateFree(state);
|
|
||||||
RoomUnlock(room);
|
|
||||||
|
|
||||||
/* TODO: Filters, to, and friends */
|
|
||||||
finish:
|
|
||||||
UserUnlock(user);
|
|
||||||
UserIdFree(id);
|
|
||||||
UserFreeList(messages);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteMessages, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *params = HttpRequestParams(args->context);
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *from = HashMapGet(params, "from");
|
|
||||||
char *limitStr = HashMapGet(params, "limit");
|
|
||||||
char *end = NULL;
|
|
||||||
char *sender = NULL;
|
|
||||||
char *serverName = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
int limit = strtol(limitStr, NULL, 10);
|
|
||||||
|
|
||||||
Array *messages = NULL;
|
|
||||||
Array *state = NULL;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!limit || limit < 0)
|
|
||||||
{
|
|
||||||
limit = 10;
|
|
||||||
}
|
|
||||||
if (!roomId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
RoomUnlock(room);
|
|
||||||
|
|
||||||
/* TODO: Filters, to, and friends */
|
|
||||||
messages = UserFetchMessages(user, limit, from, &end);
|
|
||||||
if (!messages)
|
|
||||||
{
|
|
||||||
Room *r = RoomLock(db, roomId);
|
|
||||||
Array *leaf = RoomPrevEventsGet(r);
|
|
||||||
HashMap *start = JsonValueAsObject(ArrayGet(leaf, 0));
|
|
||||||
char *startId = StrDuplicate(
|
|
||||||
JsonValueAsString(HashMapGet(start, "event_id"))
|
|
||||||
);
|
|
||||||
char *token = UserNewMessageToken(user, roomId, startId);
|
|
||||||
RoomUnlock(r);
|
|
||||||
|
|
||||||
messages = UserFetchMessages(user, limit, token, &end);
|
|
||||||
Free(token);
|
|
||||||
Free(startId);
|
|
||||||
}
|
|
||||||
response = HashMapCreate();
|
|
||||||
state = ArrayCreate();
|
|
||||||
JsonSet(response, JsonValueArray(messages), 1, "chunk");
|
|
||||||
for (i = 0; i < ArraySize(messages); i++)
|
|
||||||
{
|
|
||||||
HashMap *e = JsonValueAsObject(ArrayGet(messages, i));
|
|
||||||
if (HashMapGet(e, "state_key"))
|
|
||||||
{
|
|
||||||
ArrayAdd(state, JsonValueDuplicate(ArrayGet(messages, i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonSet(response, JsonValueArray(state), 1, "state");
|
|
||||||
JsonSet(response, JsonValueString(from), 1, "start");
|
|
||||||
if (end)
|
|
||||||
{
|
|
||||||
JsonSet(response, JsonValueString(end), 1, "end");
|
|
||||||
Free(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserFreeMessageToken(user, from);
|
|
||||||
finish:
|
|
||||||
UserUnlock(user);
|
|
||||||
UserIdFree(id);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
if (serverName)
|
|
||||||
{
|
|
||||||
Free(serverName);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Cytoplasm/HttpServer.h>
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
|
|
||||||
ROUTE_IMPL(RoutePushrules, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
response = HashMapCreate();
|
|
||||||
JsonSet(response, JsonValueObject(HashMapCreate()), 1, "global");
|
|
||||||
(void) path;
|
|
||||||
finish:
|
|
||||||
UserUnlock(user);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteRedact, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *serverName = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *eventId = ArrayGet(path, 1);
|
|
||||||
char *transId = ArrayGet(path, 2);
|
|
||||||
char *redactId = NULL;
|
|
||||||
char *sender = NULL;
|
|
||||||
char *reason = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err = NULL;
|
|
||||||
|
|
||||||
if (!roomId || !eventId || !transId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_PUT)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
reason = JsonValueAsString(HashMapGet(request, "reason"));
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
if ((response = UserGetTransaction(user, transId, "redact")))
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(redactId = RoomRedact(room, user, eventId, reason, &err)))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(response, "event_id", JsonValueString(redactId));
|
|
||||||
UserSetTransaction(user, transId, "redact", response);
|
|
||||||
Free(redactId);
|
|
||||||
finish:
|
|
||||||
RoomUnlock(room);
|
|
||||||
Free(serverName);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
JsonFree(request);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
#include <Matrix.h>
|
#include <Matrix.h>
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteRoomAliases, path, argp)
|
ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
{
|
{
|
||||||
|
@ -41,15 +40,16 @@ ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
char *msg;
|
char *msg;
|
||||||
|
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
HashMap *aliases = NULL;
|
||||||
|
HashMap *reversealias = NULL;
|
||||||
|
|
||||||
Array *alias = NULL, *arr;
|
JsonValue *val;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
DbRef *ref = NULL;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||||
{
|
{
|
||||||
msg = "Route only accepts GET.";
|
msg = "Route only accepts GET.";
|
||||||
|
@ -81,10 +81,11 @@ ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
alias = RoomReverseAlias(db, roomId);
|
ref = DbLock(db, 1, "aliases");
|
||||||
if (!alias)
|
aliases = DbJson(ref);
|
||||||
|
reversealias = JsonValueAsObject(JsonGet(aliases, 2, "id", roomId));
|
||||||
|
if (!reversealias)
|
||||||
{
|
{
|
||||||
/* We do not know about the room ID. */
|
/* We do not know about the room ID. */
|
||||||
msg = "Unknown room ID.";
|
msg = "Unknown room ID.";
|
||||||
|
@ -92,17 +93,12 @@ ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
arr = ArrayCreate();
|
|
||||||
for (i = 0; i < ArraySize(alias); i++)
|
|
||||||
{
|
|
||||||
char *str = ArrayGet(alias, i);
|
|
||||||
ArrayAdd(arr, JsonValueString(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
HashMapSet(response, "aliases", JsonValueArray(arr));
|
val = JsonGet(reversealias, 1, "aliases");
|
||||||
|
HashMapSet(response, "aliases", JsonValueDuplicate(val));
|
||||||
finish:
|
finish:
|
||||||
RoomFreeReverse(alias);
|
DbUnlock(db, ref);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteSendEvent, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL, *event = NULL;
|
|
||||||
HashMap *response = NULL, *filled = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *serverName = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *eventType = ArrayGet(path, 1);
|
|
||||||
char *transId = ArrayGet(path, 2);
|
|
||||||
char *sender = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
|
|
||||||
char *err = NULL;
|
|
||||||
|
|
||||||
if (!roomId || !eventType || !transId)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_PUT)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
if ((response = UserGetTransaction(user, transId, "send")))
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
event = RoomEventCreate(sender, eventType, NULL, JsonDuplicate(request), transId);
|
|
||||||
filled = RoomEventSend(room, event, &err);
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
if (!filled)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(
|
|
||||||
response, "event_id",
|
|
||||||
JsonValueDuplicate(HashMapGet(filled, "event_id"))
|
|
||||||
);
|
|
||||||
JsonFree(filled);
|
|
||||||
UserSetTransaction(user, transId, "send", response);
|
|
||||||
finish:
|
|
||||||
RoomUnlock(room);
|
|
||||||
Free(serverName);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
JsonFree(request);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteSendState, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *request = NULL, *event = NULL;
|
|
||||||
HashMap *response = NULL, *filled = NULL;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
CommonID *id = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
char *serverName = NULL;
|
|
||||||
|
|
||||||
char *roomId = ArrayGet(path, 0);
|
|
||||||
char *eventType = ArrayGet(path, 1);
|
|
||||||
char *stateKey = ArrayGet(path, 2);
|
|
||||||
char *sender = NULL;
|
|
||||||
|
|
||||||
Room *room = NULL;
|
|
||||||
State *state = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
if (!roomId || !eventType)
|
|
||||||
{
|
|
||||||
/* Should be impossible */
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stateKey)
|
|
||||||
{
|
|
||||||
stateKey = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
serverName = ConfigGetServerName(db);
|
|
||||||
if (!serverName)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
id = UserIdParse(UserGetName(user), serverName);
|
|
||||||
id->sigil = '@';
|
|
||||||
sender = ParserRecomposeCommonID(*id);
|
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
|
||||||
{
|
|
||||||
case HTTP_GET:
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = StateCurrent(room);
|
|
||||||
event = RoomEventFetch(
|
|
||||||
room, StateGet(state, eventType, stateKey), true
|
|
||||||
);
|
|
||||||
if (!event)
|
|
||||||
{
|
|
||||||
err = "Event could not be found.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, err);
|
|
||||||
StateFree(state);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = JsonDuplicate(JsonValueAsObject(
|
|
||||||
HashMapGet(event, "content")
|
|
||||||
));
|
|
||||||
|
|
||||||
StateFree(state);
|
|
||||||
JsonFree(event);
|
|
||||||
break;
|
|
||||||
case HTTP_PUT:
|
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
|
||||||
if (!request)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
room = RoomLock(db, roomId);
|
|
||||||
if (!RoomContainsUser(room, sender))
|
|
||||||
{
|
|
||||||
err = "User is not in the room.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
event = RoomEventCreate(
|
|
||||||
sender,
|
|
||||||
eventType, stateKey ? stateKey : "",
|
|
||||||
JsonDuplicate(request), NULL
|
|
||||||
);
|
|
||||||
filled = RoomEventSend(room, event, &err);
|
|
||||||
JsonFree(event);
|
|
||||||
|
|
||||||
if (!filled)
|
|
||||||
{
|
|
||||||
Log(LOG_ERR, "%s", err);
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
|
||||||
HashMapSet(
|
|
||||||
response, "event_id",
|
|
||||||
JsonValueDuplicate(HashMapGet(filled, "event_id"))
|
|
||||||
);
|
|
||||||
JsonFree(filled);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
finish:
|
|
||||||
RoomUnlock(room);
|
|
||||||
Free(serverName);
|
|
||||||
if (sender)
|
|
||||||
{
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
UserIdFree(id);
|
|
||||||
UserUnlock(user);
|
|
||||||
JsonFree(request);
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -52,8 +52,8 @@ ROUTE_IMPL(RouteStaticLogin, path, argp)
|
||||||
StreamPuts(stream,
|
StreamPuts(stream,
|
||||||
"function buildRequest(user, pass) {"
|
"function buildRequest(user, pass) {"
|
||||||
" var d = findGetParameter('device_id');"
|
" var d = findGetParameter('device_id');"
|
||||||
" var i = findGetParameter('initial_device_display_name');"
|
" var i = findGetParameter('initial_device_display_name');"
|
||||||
" var r = findGetParameter('refresh_token') === 'true';"
|
" var r = findGetParameter('refresh_token') === 'true';"
|
||||||
" var request = {};"
|
" var request = {};"
|
||||||
" request['type'] = 'm.login.password';"
|
" request['type'] = 'm.login.password';"
|
||||||
" request['identifier'] = {"
|
" request['identifier'] = {"
|
||||||
|
@ -88,9 +88,9 @@ ROUTE_IMPL(RouteStaticLogin, path, argp)
|
||||||
StreamPuts(stream,
|
StreamPuts(stream,
|
||||||
"onFormSubmit('login-form', (frm) => {"
|
"onFormSubmit('login-form', (frm) => {"
|
||||||
" var user = document.getElementById('user').value;"
|
" var user = document.getElementById('user').value;"
|
||||||
" var pass = document.getElementById('password').value;"
|
" var pass = document.getElementById('password').value;"
|
||||||
" if (!user || !pass) {"
|
" if (!user || !pass) {"
|
||||||
" setFormError('Please provide a username and password.');"
|
" setFormError('Please provide a username and password.');"
|
||||||
" return;"
|
" return;"
|
||||||
" }"
|
" }"
|
||||||
" setFormError(null);"
|
" setFormError(null);"
|
||||||
|
|
|
@ -69,7 +69,7 @@ ROUTE_IMPL(RouteStaticResources, path, argp)
|
||||||
"function jsonRequest(meth, url, json, cb) {"
|
"function jsonRequest(meth, url, json, cb) {"
|
||||||
" var xhr = new XMLHttpRequest();"
|
" var xhr = new XMLHttpRequest();"
|
||||||
" xhr.open(meth, url);"
|
" xhr.open(meth, url);"
|
||||||
" xhr.setRequestHeader('Content-Type', 'application/json');"
|
" xhr.setRequestHeader('Content-Type', 'application/json');"
|
||||||
" xhr.onreadystatechange = () => {"
|
" xhr.onreadystatechange = () => {"
|
||||||
" if (xhr.readyState == 4) {"
|
" if (xhr.readyState == 4) {"
|
||||||
" cb(xhr);"
|
" cb(xhr);"
|
||||||
|
|
|
@ -1,280 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
|
||||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <Cytoplasm/HttpServer.h>
|
|
||||||
#include <Routes.h>
|
|
||||||
|
|
||||||
#include <Schema/SyncResponse.h>
|
|
||||||
#include <Schema/Filter.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Util.h>
|
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
|
|
||||||
#include <Filter.h>
|
|
||||||
#include <State.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
static ClientEventWithoutRoomID
|
|
||||||
ClientfyEventSync(HashMap *pdu)
|
|
||||||
{
|
|
||||||
ClientEventWithoutRoomID ret = { 0 };
|
|
||||||
char *ignored;
|
|
||||||
ClientEventWithoutRoomIDFromJson(pdu, &ret, &ignored);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteSync, path, argp)
|
|
||||||
{
|
|
||||||
RouteArgs *args = argp;
|
|
||||||
Db *db = args->matrixArgs->db;
|
|
||||||
|
|
||||||
HashMap *params = NULL;
|
|
||||||
HashMap *response = NULL;
|
|
||||||
SyncResponse sync = { 0 };
|
|
||||||
Filter *filterData = NULL;
|
|
||||||
|
|
||||||
Array *accountData;
|
|
||||||
Array *invites;
|
|
||||||
Array *joins;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
User *user = NULL;
|
|
||||||
char *token = NULL;
|
|
||||||
char *prevBatch = NULL;
|
|
||||||
char *nextBatch = NULL;
|
|
||||||
char *currBatch = NULL;
|
|
||||||
char *timeout = NULL;
|
|
||||||
char *filter = NULL;
|
|
||||||
|
|
||||||
char *err;
|
|
||||||
|
|
||||||
int timeoutDuration;
|
|
||||||
|
|
||||||
/* TODO: Respect `timeout', (and stop when something is
|
|
||||||
* pushed, maybe by 'polling' the database? sounds like
|
|
||||||
* a bad idea) */
|
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
|
||||||
{
|
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
|
||||||
if (response)
|
|
||||||
{
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
if (!user)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
params = HttpRequestParams(args->context);
|
|
||||||
prevBatch = HashMapGet(params, "since");
|
|
||||||
timeout = HashMapGet(params, "timeout");
|
|
||||||
filter = HashMapGet(params, "filter");
|
|
||||||
timeoutDuration = timeout ? atoi(timeout) : 0;
|
|
||||||
|
|
||||||
if (filter)
|
|
||||||
{
|
|
||||||
char *userName = UserGetName(user);
|
|
||||||
if (!(filterData = FilterDecode(db, userName, filter, &err)))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prevBatch)
|
|
||||||
{
|
|
||||||
prevBatch = NULL;
|
|
||||||
nextBatch = UserInitSyncDiff(user);
|
|
||||||
UserFillSyncDiff(user, nextBatch);
|
|
||||||
}
|
|
||||||
else if (timeout && timeoutDuration)
|
|
||||||
{
|
|
||||||
char *name = StrDuplicate(UserGetName(user));
|
|
||||||
|
|
||||||
UserUnlock(user);
|
|
||||||
UserAwaitNotification(name, timeoutDuration);
|
|
||||||
Free(name);
|
|
||||||
user = UserAuthenticate(db, token);
|
|
||||||
}
|
|
||||||
currBatch = prevBatch ? prevBatch : nextBatch;
|
|
||||||
|
|
||||||
/* TODO: I only am manually parsing this because j2s does not support
|
|
||||||
* a hashmap of unknown keys pointing to a known type. */
|
|
||||||
sync.rooms.invite = NULL;
|
|
||||||
sync.rooms.join = NULL;
|
|
||||||
sync.account_data.events = NULL;
|
|
||||||
sync.device_one_time_keys_count = UserGetOnetimeCounts(user);
|
|
||||||
|
|
||||||
/* account data */
|
|
||||||
accountData = UserGetAccountDataSync(user, currBatch);
|
|
||||||
if (ArraySize(accountData) > 0)
|
|
||||||
{
|
|
||||||
sync.account_data.events = ArrayCreate();
|
|
||||||
}
|
|
||||||
for (i = 0; i < ArraySize(accountData); i++)
|
|
||||||
{
|
|
||||||
char *key = ArrayGet(accountData, i);
|
|
||||||
Event *event = Malloc(sizeof(*event));
|
|
||||||
event->type = StrDuplicate(key);
|
|
||||||
event->content = UserGetAccountData(user, key);
|
|
||||||
|
|
||||||
ArrayAdd(sync.account_data.events, event);
|
|
||||||
}
|
|
||||||
UserFreeList(accountData);
|
|
||||||
|
|
||||||
/* invites */
|
|
||||||
invites = UserGetInvites(user, currBatch);
|
|
||||||
if (ArraySize(invites) > 0)
|
|
||||||
{
|
|
||||||
sync.rooms.invite = HashMapCreate();
|
|
||||||
}
|
|
||||||
for (i = 0; i < ArraySize(invites); i++)
|
|
||||||
{
|
|
||||||
char *roomId = ArrayGet(invites, i);
|
|
||||||
InvitedRooms *invited;
|
|
||||||
|
|
||||||
if (IsRoomFiltered(filterData, roomId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
invited = Malloc(sizeof(*invited));
|
|
||||||
memset(invited, 0, sizeof(*invited));
|
|
||||||
|
|
||||||
// TODO: Populate the invitestate
|
|
||||||
invited->invite_state.events = ArrayCreate();
|
|
||||||
HashMapSet(sync.rooms.invite, roomId, invited);
|
|
||||||
}
|
|
||||||
UserFreeList(invites);
|
|
||||||
|
|
||||||
/* Joins */
|
|
||||||
joins = UserGetJoins(user, currBatch);
|
|
||||||
if (ArraySize(joins) > 0)
|
|
||||||
{
|
|
||||||
sync.rooms.join = HashMapCreate();
|
|
||||||
}
|
|
||||||
for (i = 0; i < ArraySize(joins); i++)
|
|
||||||
{
|
|
||||||
char *roomId = ArrayGet(joins, i);
|
|
||||||
JoinedRooms *joined;
|
|
||||||
char *firstEvent = NULL;
|
|
||||||
char *type, *key, *id;
|
|
||||||
State *state;
|
|
||||||
Array *el;
|
|
||||||
size_t j;
|
|
||||||
Room *r;
|
|
||||||
|
|
||||||
if (IsRoomFiltered(filterData, roomId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
joined = Malloc(sizeof(*joined));
|
|
||||||
memset(joined, 0, sizeof(*joined));
|
|
||||||
|
|
||||||
el = UserGetEvents(user, currBatch, roomId);
|
|
||||||
r = RoomLock(db, roomId);
|
|
||||||
state = StateCurrent(r);
|
|
||||||
|
|
||||||
joined->timeline.events = ArrayCreate();
|
|
||||||
for (j = 0; j < ArraySize(el); j++)
|
|
||||||
{
|
|
||||||
char *event = ArrayGet(el, j);
|
|
||||||
HashMap *eventObj = RoomEventFetch(r, event, true);
|
|
||||||
HashMap *filteredObj = FilterApply(filterData, eventObj);
|
|
||||||
|
|
||||||
if (filteredObj)
|
|
||||||
{
|
|
||||||
ClientEventWithoutRoomID rc = ClientfyEventSync(filteredObj);
|
|
||||||
ClientEventWithoutRoomID *c = Malloc(sizeof(*c));
|
|
||||||
memcpy(c, &rc, sizeof(*c));
|
|
||||||
|
|
||||||
if (!firstEvent)
|
|
||||||
{
|
|
||||||
firstEvent = c->event_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayAdd(joined->timeline.events, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFree(eventObj);
|
|
||||||
JsonFree(filteredObj);
|
|
||||||
}
|
|
||||||
joined->timeline.prev_batch = UserNewMessageToken(
|
|
||||||
user, roomId, firstEvent
|
|
||||||
);
|
|
||||||
// TODO: Don't shove the entire state.
|
|
||||||
// That's a recipe for disaster, especially on large rooms.
|
|
||||||
joined->state.events = ArrayCreate();
|
|
||||||
while (StateIterate(state, &type, &key, (void **) &id))
|
|
||||||
{
|
|
||||||
HashMap *e = RoomEventFetch(r, id, true);
|
|
||||||
|
|
||||||
ArrayAdd(joined->state.events, JsonValueObject(e));
|
|
||||||
Free(type);
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
StateFree(state);
|
|
||||||
RoomUnlock(r);
|
|
||||||
UserFreeList(el);
|
|
||||||
|
|
||||||
HashMapSet(sync.rooms.join, roomId, joined);
|
|
||||||
}
|
|
||||||
UserFreeList(joins);
|
|
||||||
|
|
||||||
if (prevBatch)
|
|
||||||
{
|
|
||||||
/* TODO: Should we be dropping syncs? */
|
|
||||||
//UserDropSync(user, prevBatch);
|
|
||||||
nextBatch = UserInitSyncDiff(user);
|
|
||||||
}
|
|
||||||
sync.next_batch = nextBatch;
|
|
||||||
response = SyncResponseToJson(&sync);
|
|
||||||
SyncResponseFree(&sync);
|
|
||||||
(void) i;
|
|
||||||
finish:
|
|
||||||
FilterDestroy(filterData);
|
|
||||||
UserUnlock(user);
|
|
||||||
(void) path;
|
|
||||||
return response;
|
|
||||||
}
|
|
|
@ -28,52 +28,10 @@
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
|
#include <User.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <Config.h>
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
|
|
||||||
static void
|
|
||||||
SendMembership(Db *db, User *user)
|
|
||||||
{
|
|
||||||
char *displayname = UserGetProfile(user, "displayname");
|
|
||||||
char *avatar_url = UserGetProfile(user, "avatar_url");
|
|
||||||
Array *joins = UserListJoins(user);
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
char *server_name = ConfigGetServerName(db);
|
|
||||||
|
|
||||||
CommonID *user_id = UserIdParse(UserGetName(user), server_name);
|
|
||||||
char *sender = ParserRecomposeCommonID(*user_id);
|
|
||||||
Free(server_name);
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(joins); i++)
|
|
||||||
{
|
|
||||||
char *room_id = ArrayGet(joins, i);
|
|
||||||
Room *room = RoomLock(db, room_id);
|
|
||||||
HashMap *content = HashMapCreate();
|
|
||||||
HashMap *membership = RoomEventCreate(
|
|
||||||
sender, "m.room.member", sender,
|
|
||||||
content, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
HashMapSet(content, "membership", JsonValueString("join"));
|
|
||||||
HashMapSet(content, "displayname", JsonValueString(displayname));
|
|
||||||
HashMapSet(content, "avatar_url", JsonValueString(avatar_url));
|
|
||||||
|
|
||||||
JsonFree(RoomEventSend(room, membership, NULL));
|
|
||||||
JsonFree(membership);
|
|
||||||
|
|
||||||
RoomUnlock(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserFreeList(joins);
|
|
||||||
UserIdFree(user_id);
|
|
||||||
Free(sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteUserProfile, path, argp)
|
ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -184,8 +142,6 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: This may change in the future. */
|
|
||||||
entry = ArrayGet(path, 1);
|
entry = ArrayGet(path, 1);
|
||||||
if (StrEquals(entry, "displayname") ||
|
if (StrEquals(entry, "displayname") ||
|
||||||
StrEquals(entry, "avatar_url"))
|
StrEquals(entry, "avatar_url"))
|
||||||
|
@ -197,7 +153,6 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
/* TODO: Make UserSetProfile notify other parties of
|
/* TODO: Make UserSetProfile notify other parties of
|
||||||
* the change */
|
* the change */
|
||||||
UserSetProfile(user, entry, value);
|
UserSetProfile(user, entry, value);
|
||||||
SendMembership(db, user);
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,6 @@ ROUTE_IMPL(RouteVersions, path, argp)
|
||||||
(void) argp;
|
(void) argp;
|
||||||
|
|
||||||
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
|
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
|
||||||
DECLARE_SPEC_VERSION("v1.0");
|
|
||||||
DECLARE_SPEC_VERSION("v1.1");
|
|
||||||
|
|
||||||
DECLARE_SPEC_VERSION("v1.2");
|
DECLARE_SPEC_VERSION("v1.2");
|
||||||
DECLARE_SPEC_VERSION("v1.3");
|
DECLARE_SPEC_VERSION("v1.3");
|
||||||
DECLARE_SPEC_VERSION("v1.4");
|
DECLARE_SPEC_VERSION("v1.4");
|
||||||
|
|
552
src/State.c
552
src/State.c
|
@ -26,326 +26,26 @@
|
||||||
#include <State.h>
|
#include <State.h>
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
|
||||||
#include <Cytoplasm/Base64.h>
|
|
||||||
#include <Cytoplasm/Array.h>
|
#include <Cytoplasm/Array.h>
|
||||||
#include <Cytoplasm/Str.h>
|
|
||||||
#include <Cytoplasm/Sha.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <Event.h>
|
|
||||||
#include <Room.h>
|
#include <Room.h>
|
||||||
|
#include <Event.h>
|
||||||
|
|
||||||
struct State {
|
static HashMap *
|
||||||
/* A hashmap of event IDs for a state */
|
StateResolveV1(Array * states)
|
||||||
HashMap *table;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static State *
|
|
||||||
InitialiseState(void)
|
|
||||||
{
|
{
|
||||||
State *ret = Malloc(sizeof(*ret));
|
(void) states;
|
||||||
|
return NULL;
|
||||||
ret->table = HashMapCreate();
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static HashMap *
|
||||||
V1Cmp(void *a, void *b)
|
|
||||||
{
|
|
||||||
HashMap *e1 = a, *e2 = b;
|
|
||||||
int64_t depth1, depth2;
|
|
||||||
|
|
||||||
depth1 =
|
|
||||||
JsonValueAsInteger(JsonGet(e1, 1, "depth"));
|
|
||||||
depth2 =
|
|
||||||
JsonValueAsInteger(JsonGet(e2, 1, "depth"));
|
|
||||||
|
|
||||||
if (depth1 > depth2)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (depth1 < depth2)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
char *e1id =
|
|
||||||
JsonValueAsString(JsonGet(e1, 1, "event_id"));
|
|
||||||
char *e2id =
|
|
||||||
JsonValueAsString(JsonGet(e2, 1, "event_id"));
|
|
||||||
|
|
||||||
/* Matrix, *seriously*? */
|
|
||||||
unsigned char *sha1 = Sha1(e1id);
|
|
||||||
unsigned char *sha2 = Sha1(e2id);
|
|
||||||
char *str1 = ShaToHex(sha1, HASH_SHA1);
|
|
||||||
char *str2 = ShaToHex(sha2, HASH_SHA1);
|
|
||||||
int ret = strcmp(str1, str2) * -1;
|
|
||||||
|
|
||||||
Free(str1);
|
|
||||||
Free(str2);
|
|
||||||
Free(sha1);
|
|
||||||
Free(sha2);
|
|
||||||
|
|
||||||
/* Descending */
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
EncodeStateTuple(char *type, char *stateKey)
|
|
||||||
{
|
|
||||||
size_t typeLen = type ? strlen(type) : 0;
|
|
||||||
size_t stateKeyLen = stateKey ? strlen(stateKey) : 0;
|
|
||||||
size_t length = typeLen + 1 + stateKeyLen + 1;
|
|
||||||
char *tuple = Malloc(length);
|
|
||||||
char *base64;
|
|
||||||
|
|
||||||
memset(tuple, '\0', length);
|
|
||||||
memcpy(tuple, type, typeLen);
|
|
||||||
memcpy(tuple + typeLen + 1, stateKey, stateKeyLen);
|
|
||||||
|
|
||||||
base64 = Base64Encode((const char *) tuple, length);
|
|
||||||
Free(tuple);
|
|
||||||
return base64;
|
|
||||||
}
|
|
||||||
static void
|
|
||||||
DecodeStateTuple(char *base64, char **key, char **val)
|
|
||||||
{
|
|
||||||
size_t base64Size = base64 ? strlen(base64) : 0;
|
|
||||||
char *decodeBuffer = NULL;
|
|
||||||
if (!base64 || !key || !val)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeBuffer = Base64Decode((const char *) base64, base64Size);
|
|
||||||
*key = StrDuplicate(decodeBuffer);
|
|
||||||
*val = StrDuplicate(decodeBuffer + strlen(decodeBuffer) + 1);
|
|
||||||
|
|
||||||
Free(decodeBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
BuildBaseAndConflictV1(Room *room, Array *states, State *R, HashMap *conflicts)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
char *type = NULL, *key = NULL;
|
|
||||||
for (i = 0; i < ArraySize(states); i++)
|
|
||||||
{
|
|
||||||
char *event_id;
|
|
||||||
State *state = ArrayGet(states, i);
|
|
||||||
while (StateIterate(state, &type, &key, (void **) &event_id))
|
|
||||||
{
|
|
||||||
char *tuple = EncodeStateTuple(type, key);
|
|
||||||
if (StateGet(R, type, key) || HashMapGet(conflicts, tuple))
|
|
||||||
{
|
|
||||||
Array *arr;
|
|
||||||
HashMap *hm;
|
|
||||||
|
|
||||||
/* Conflicts! */
|
|
||||||
StateSet(R, type, key, NULL);
|
|
||||||
arr = HashMapGet(conflicts, tuple);
|
|
||||||
if (!arr)
|
|
||||||
{
|
|
||||||
arr = ArrayCreate();
|
|
||||||
}
|
|
||||||
hm = RoomEventFetch(room, event_id, false);
|
|
||||||
ArrayAdd(arr, hm);
|
|
||||||
HashMapSet(conflicts, tuple, arr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Add to R */
|
|
||||||
StateSet(R, type, key, event_id);
|
|
||||||
}
|
|
||||||
Free(tuple);
|
|
||||||
Free(type);
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static void
|
|
||||||
FixoutConflictV1(char *t, Room *room, State *R, HashMap *conflicts)
|
|
||||||
{
|
|
||||||
HashMap *first;
|
|
||||||
Array *state_keys, *conflicting, *events;
|
|
||||||
char *tuple;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
events = ArrayCreate();
|
|
||||||
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
|
|
||||||
{
|
|
||||||
char *type, *key;
|
|
||||||
DecodeStateTuple(tuple, &type, &key);
|
|
||||||
|
|
||||||
if (StrEquals(type, t))
|
|
||||||
{
|
|
||||||
for (i = 0; i < ArraySize(conflicting); i++)
|
|
||||||
{
|
|
||||||
HashMap *event = ArrayGet(conflicting, i);
|
|
||||||
ArrayAdd(events, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Free(type);
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
ArraySort(events, V1Cmp);
|
|
||||||
/* Add first event. */
|
|
||||||
first = ArrayDelete(events, 0);
|
|
||||||
StateSet(
|
|
||||||
R,
|
|
||||||
JsonValueAsString(JsonGet(first, 1, "type")),
|
|
||||||
JsonValueAsString(JsonGet(first, 1, "state_key")),
|
|
||||||
JsonValueAsString(JsonGet(first, 1, "event_id"))
|
|
||||||
);
|
|
||||||
JsonFree(first);
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(events); i++)
|
|
||||||
{
|
|
||||||
HashMap *event = ArrayGet(events, i);
|
|
||||||
PduV1 pdu;
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
PduV1FromJson(event, &pdu, &msg);
|
|
||||||
if (RoomAuthoriseEventV1(room, pdu, R, NULL))
|
|
||||||
{
|
|
||||||
StateSet(R, pdu.type, pdu.state_key, pdu.event_id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PduV1Free(&pdu);
|
|
||||||
JsonFree(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(void) msg;
|
|
||||||
PduV1Free(&pdu);
|
|
||||||
JsonFree(event);
|
|
||||||
}
|
|
||||||
ArrayFree(events);
|
|
||||||
|
|
||||||
state_keys = ArrayCreate();
|
|
||||||
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
|
|
||||||
{
|
|
||||||
char *type, *key;
|
|
||||||
DecodeStateTuple(tuple, &type, &key);
|
|
||||||
|
|
||||||
if (StrEquals(type, t))
|
|
||||||
{
|
|
||||||
ArrayAdd(state_keys, key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
Free(type);
|
|
||||||
}
|
|
||||||
for (i = 0; i < ArraySize(state_keys); i++)
|
|
||||||
{
|
|
||||||
char *state_key = ArrayGet(state_keys, i);
|
|
||||||
char *tuple = EncodeStateTuple(t, state_key);
|
|
||||||
Array *conflict = HashMapDelete(conflicts, tuple);
|
|
||||||
|
|
||||||
/* All of the other values are already freed */
|
|
||||||
ArrayFree(conflict);
|
|
||||||
|
|
||||||
Free(state_key);
|
|
||||||
Free(tuple);
|
|
||||||
}
|
|
||||||
ArrayFree(state_keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static State *
|
|
||||||
StateResolveV1(Room * room, Array * states)
|
|
||||||
{
|
|
||||||
State *R = InitialiseState();
|
|
||||||
HashMap *conflicts = HashMapCreate();
|
|
||||||
Array *events = NULL, *conflicting = NULL;
|
|
||||||
char *type, *key, *tuple;
|
|
||||||
|
|
||||||
BuildBaseAndConflictV1(room, states, R, conflicts);
|
|
||||||
|
|
||||||
/* R and conflicts are now configured */
|
|
||||||
FixoutConflictV1("m.room.power_levels", room, R, conflicts);
|
|
||||||
FixoutConflictV1("m.room.join_rules", room, R, conflicts);
|
|
||||||
FixoutConflictV1("m.room.member", room, R, conflicts);
|
|
||||||
|
|
||||||
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
|
|
||||||
{
|
|
||||||
ssize_t i;
|
|
||||||
DecodeStateTuple(tuple, &type, &key);
|
|
||||||
|
|
||||||
ArraySort(conflicting, V1Cmp);
|
|
||||||
for (i = ArraySize(conflicting) - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
HashMap *event = ArrayGet(events, i);
|
|
||||||
PduV1 pdu = { 0 };
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!PduV1FromJson(event, &pdu, &msg))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RoomAuthoriseEventV1(room, pdu, R, NULL))
|
|
||||||
{
|
|
||||||
StateSet(R, pdu.type, pdu.state_key, pdu.event_id);
|
|
||||||
PduV1Free(&pdu);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(void) msg;
|
|
||||||
PduV1Free(&pdu);
|
|
||||||
}
|
|
||||||
Free(type);
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (HashMapIterate(conflicts, &tuple, (void **) &conflicting))
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
for (i = 0; i < ArraySize(conflicting); i++)
|
|
||||||
{
|
|
||||||
JsonFree(ArrayGet(conflicting, i));
|
|
||||||
}
|
|
||||||
ArrayFree(conflicting);
|
|
||||||
}
|
|
||||||
HashMapFree(conflicts);
|
|
||||||
|
|
||||||
return R;
|
|
||||||
}
|
|
||||||
|
|
||||||
static State *
|
|
||||||
StateResolveV2(Array * states)
|
StateResolveV2(Array * states)
|
||||||
{
|
{
|
||||||
(void) states;
|
(void) states;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static State *
|
HashMap *
|
||||||
StateFromPrevs(Room *room, Array *states)
|
|
||||||
{
|
|
||||||
switch (RoomVersionGet(room))
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
return StateResolveV1(room, states);
|
|
||||||
default:
|
|
||||||
return StateResolveV2(states);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
IsRejected(HashMap *pdu)
|
|
||||||
{
|
|
||||||
JsonValue *val = JsonGet(pdu, 2, "unsigned", "status");
|
|
||||||
|
|
||||||
return StrEquals(JsonValueAsString(val), "rejected");
|
|
||||||
}
|
|
||||||
|
|
||||||
State *
|
|
||||||
StateResolve(Room * room, HashMap * event)
|
StateResolve(Room * room, HashMap * event)
|
||||||
{
|
{
|
||||||
Array *states;
|
Array *states;
|
||||||
|
@ -353,252 +53,36 @@ StateResolve(Room * room, HashMap * event)
|
||||||
|
|
||||||
Array *prevEvents;
|
Array *prevEvents;
|
||||||
|
|
||||||
State *ret_state;
|
|
||||||
|
|
||||||
char *room_id, *event_id;
|
|
||||||
|
|
||||||
Db *db;
|
|
||||||
|
|
||||||
if (!room || !event)
|
if (!room || !event)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
db = RoomGetDB(room);
|
/* TODO: Return cached state if it exists */
|
||||||
room_id = JsonValueAsString(HashMapGet(event, "room_id"));
|
|
||||||
event_id = JsonValueAsString(HashMapGet(event, "event_id"));
|
|
||||||
if (!room_id || !event_id)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (DbExists(db, 4, "rooms", room_id, "state", event_id))
|
|
||||||
{
|
|
||||||
DbRef *ref = DbLock(db, 4,
|
|
||||||
"rooms", room_id, "state", event_id
|
|
||||||
);
|
|
||||||
ret_state = StateDeserialise(DbJson(ref));
|
|
||||||
DbUnlock(db, ref);
|
|
||||||
if (ret_state)
|
|
||||||
{
|
|
||||||
return ret_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If a DB error stops us from getting an existing state,
|
|
||||||
* recompute it. */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
states = ArrayCreate();
|
states = ArrayCreate();
|
||||||
if (!states)
|
if (!states)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
prevEvents = JsonValueAsArray(HashMapGet(event, "prev_events"));
|
|
||||||
|
prevEvents = HashMapGet(event, "prev_events");
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(prevEvents); i++)
|
for (i = 0; i < ArraySize(prevEvents); i++)
|
||||||
{
|
{
|
||||||
HashMap *prevEvent = RoomEventFetch(
|
HashMap *prevEvent = ArrayGet(prevEvents, i);
|
||||||
room, JsonValueAsString(ArrayGet(prevEvents, i)),
|
HashMap *state = StateResolve(room, prevEvent);
|
||||||
false
|
|
||||||
);
|
|
||||||
State *state = StateResolve(room, prevEvent);
|
|
||||||
|
|
||||||
if (HashMapGet(prevEvent, "state_key") && !IsRejected(prevEvent))
|
/* TODO: Apply prevEvent to state if it is a state event */
|
||||||
{
|
|
||||||
StateSet(
|
|
||||||
state,
|
|
||||||
JsonValueAsString(HashMapGet(prevEvent, "type")),
|
|
||||||
JsonValueAsString(HashMapGet(prevEvent, "state_key")),
|
|
||||||
JsonValueAsString(HashMapGet(prevEvent, "event_id"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayAdd(states, state);
|
|
||||||
JsonFree(prevEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret_state = StateFromPrevs(room, states);
|
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(states); i++)
|
|
||||||
{
|
|
||||||
State *state = ArrayGet(states, i);
|
|
||||||
StateFree(state);
|
|
||||||
}
|
|
||||||
ArrayFree(states);
|
|
||||||
|
|
||||||
if (ret_state)
|
|
||||||
{
|
|
||||||
HashMap *json = StateSerialise(ret_state);
|
|
||||||
DbRef *ref = DbCreate(db, 4, "rooms", room_id, "state", event_id);
|
|
||||||
DbJsonSet(ref, json);
|
|
||||||
JsonFree(json);
|
|
||||||
DbUnlock(db, ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret_state;
|
|
||||||
}
|
|
||||||
State *
|
|
||||||
StateCurrent(Room *room)
|
|
||||||
{
|
|
||||||
Array *prevEvents;
|
|
||||||
Array *states;
|
|
||||||
size_t i;
|
|
||||||
State *ret;
|
|
||||||
if (!room)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevEvents = RoomPrevEventsGet(room);
|
|
||||||
states = ArrayCreate();
|
|
||||||
for (i = 0; i < ArraySize(prevEvents); i++)
|
|
||||||
{
|
|
||||||
HashMap *event =
|
|
||||||
JsonValueAsObject(ArrayGet(prevEvents, i));
|
|
||||||
State *state = StateResolve(room, event);
|
|
||||||
|
|
||||||
if (HashMapGet(event, "state_key") && !IsRejected(event))
|
|
||||||
{
|
|
||||||
StateSet(
|
|
||||||
state,
|
|
||||||
JsonValueAsString(HashMapGet(event, "type")),
|
|
||||||
JsonValueAsString(HashMapGet(event, "state_key")),
|
|
||||||
JsonValueAsString(HashMapGet(event, "event_id"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayAdd(states, state);
|
ArrayAdd(states, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = StateFromPrevs(room, states);
|
switch (RoomVersionGet(room))
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(states); i++)
|
|
||||||
{
|
{
|
||||||
State *state = ArrayGet(states, i);
|
case 1:
|
||||||
StateFree(state);
|
return StateResolveV1(states);
|
||||||
|
default:
|
||||||
|
return StateResolveV2(states);
|
||||||
}
|
}
|
||||||
ArrayFree(states);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
StateIterate(State *state, char **type, char **key, void **event)
|
|
||||||
{
|
|
||||||
bool ret;
|
|
||||||
char *tuple;
|
|
||||||
if (!state || !type || !key || !event)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = HashMapIterate(state->table, &tuple, event);
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
DecodeStateTuple(tuple, type, key);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
StateGet(State *state, char *type, char *key)
|
|
||||||
{
|
|
||||||
char *tableKey;
|
|
||||||
char *ret;
|
|
||||||
if (!state || !type || !key)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
tableKey = EncodeStateTuple(type, key);
|
|
||||||
ret = HashMapGet(state->table, tableKey);
|
|
||||||
|
|
||||||
Free(tableKey);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
StateSet(State *state, char *type, char *key, char *event)
|
|
||||||
{
|
|
||||||
char *tableKey;
|
|
||||||
if (!state || !type || !key)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tableKey = EncodeStateTuple(type, key);
|
|
||||||
Free(HashMapDelete(state->table, tableKey));
|
|
||||||
if (event)
|
|
||||||
{
|
|
||||||
HashMapSet(state->table, tableKey, StrDuplicate(event));
|
|
||||||
}
|
|
||||||
Free(tableKey);
|
|
||||||
}
|
|
||||||
void
|
|
||||||
StateFree(State *state)
|
|
||||||
{
|
|
||||||
char *tuple;
|
|
||||||
char *eventID;
|
|
||||||
|
|
||||||
if (!state)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (HashMapIterate(state->table, &tuple, (void **) &eventID))
|
|
||||||
{
|
|
||||||
Free(eventID);
|
|
||||||
}
|
|
||||||
HashMapFree(state->table);
|
|
||||||
Free(state);
|
|
||||||
}
|
|
||||||
State *
|
|
||||||
StateDeserialise(HashMap *json_state)
|
|
||||||
{
|
|
||||||
State *raw_state;
|
|
||||||
|
|
||||||
char *state_type;
|
|
||||||
JsonValue *state_keys;
|
|
||||||
|
|
||||||
if (!json_state)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_state = InitialiseState();
|
|
||||||
|
|
||||||
while (HashMapIterate(json_state, &state_type, (void **) &state_keys))
|
|
||||||
{
|
|
||||||
HashMap *state_keys_obj = JsonValueAsObject(state_keys);
|
|
||||||
char *state_key;
|
|
||||||
JsonValue *event_id;
|
|
||||||
|
|
||||||
while (HashMapIterate(state_keys_obj, &state_key, (void **) &event_id))
|
|
||||||
{
|
|
||||||
char *eid_string = JsonValueAsString(event_id);
|
|
||||||
|
|
||||||
StateSet(raw_state, state_type, state_key, eid_string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw_state;
|
|
||||||
}
|
|
||||||
HashMap *
|
|
||||||
StateSerialise(State *rawState)
|
|
||||||
{
|
|
||||||
HashMap *returned;
|
|
||||||
char *type, *key, *event;
|
|
||||||
if (!rawState)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
returned = HashMapCreate();
|
|
||||||
while (StateIterate(rawState, &type, &key, (void **) &event))
|
|
||||||
{
|
|
||||||
JsonSet(returned, JsonValueString(event), 2, type, key);
|
|
||||||
Free(type);
|
|
||||||
Free(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return returned;
|
|
||||||
}
|
}
|
||||||
|
|
10
src/Uia.c
10
src/Uia.c
|
@ -312,16 +312,14 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: The type may sometimes be omitted. This means it is implied
|
|
||||||
* from context. */
|
|
||||||
val = HashMapGet(auth, "type");
|
val = HashMapGet(auth, "type");
|
||||||
|
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
/* TODO: Clients may want to retrieve the flowlist.
|
msg = "'auth->type' is unset or not a string.";
|
||||||
* Still stupid, but eh. */
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
ret = BuildResponse(flows, db, response, session, dbRef);
|
ret = 0;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1355
src/User.c
1355
src/User.c
File diff suppressed because it is too large
Load diff
|
@ -79,16 +79,6 @@
|
||||||
*/
|
*/
|
||||||
extern int CanonicalJsonEncode(HashMap *, Stream *);
|
extern int CanonicalJsonEncode(HashMap *, Stream *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes a JSON object encoded as Canonical JSON's SHA-256
|
|
||||||
* hash.
|
|
||||||
*
|
|
||||||
* This function returns a SHA-256 hexstream stored on the heap,
|
|
||||||
* which will need to be freed with
|
|
||||||
* .Fn Free .
|
|
||||||
*/
|
|
||||||
extern unsigned char * CanonicalJsonHash(HashMap *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a JSON value following the rules of Canonical JSON.
|
* Encode a JSON value following the rules of Canonical JSON.
|
||||||
* See the documentation for
|
* See the documentation for
|
||||||
|
|
|
@ -98,16 +98,6 @@ extern void ConfigLock(Db *, Config *);
|
||||||
*/
|
*/
|
||||||
extern int ConfigUnlock(Config *);
|
extern int ConfigUnlock(Config *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the configuration's server name from a database.
|
|
||||||
* This is an utility function that masks behind
|
|
||||||
* .Fn ConfigLock ,
|
|
||||||
* and the value returned lives on the heap, and must be freed
|
|
||||||
* with
|
|
||||||
* .Fn Free .
|
|
||||||
*/
|
|
||||||
extern char * ConfigGetServerName(Db *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a ConfigLogLevel into a valid syslog level.
|
* Converts a ConfigLogLevel into a valid syslog level.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Db.h>
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* @Nm Filter
|
* @Nm Filter
|
||||||
|
@ -48,22 +47,4 @@
|
||||||
extern HashMap *
|
extern HashMap *
|
||||||
FilterApply(Filter *, HashMap *);
|
FilterApply(Filter *, HashMap *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if a room would be filtered out by a filter given in.
|
|
||||||
*/
|
|
||||||
extern bool IsRoomFiltered(Filter *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a filter from a JSON stream to decode or an ID that was
|
|
||||||
* registered using the filter API, and may return an optional error
|
|
||||||
* string, if not set to NULL.
|
|
||||||
*/
|
|
||||||
extern Filter * FilterDecode(Db *, char *, char *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees all memory created by
|
|
||||||
* .Fn FilterDecode .
|
|
||||||
*/
|
|
||||||
extern void FilterDestroy(Filter *);
|
|
||||||
|
|
||||||
#endif /* TELODENDRIA_FILTER_H */
|
#endif /* TELODENDRIA_FILTER_H */
|
||||||
|
|
|
@ -39,9 +39,8 @@
|
||||||
|
|
||||||
#include <Cytoplasm/HttpServer.h>
|
#include <Cytoplasm/HttpServer.h>
|
||||||
#include <Cytoplasm/HttpRouter.h>
|
#include <Cytoplasm/HttpRouter.h>
|
||||||
#include <Cytoplasm/HashMap.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
|
||||||
#include <Cytoplasm/Log.h>
|
#include <Cytoplasm/Log.h>
|
||||||
|
#include <Cytoplasm/HashMap.h>
|
||||||
|
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
|
@ -155,25 +154,4 @@ extern HashMap * MatrixRateLimit(HttpServerContext *, Db *);
|
||||||
*/
|
*/
|
||||||
extern HashMap * MatrixClientWellKnown(char *, char *);
|
extern HashMap * MatrixClientWellKnown(char *, char *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a dot-separated path (as defined by the Specification) and
|
|
||||||
* tries to retrieve it from a JSON object hashmap, failing with NULL
|
|
||||||
* if it cannot be found, like Cytoplasm's
|
|
||||||
* .Fn JsonGet .
|
|
||||||
*/
|
|
||||||
extern JsonValue * MatrixGetJSON(HashMap *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a value given a dot-separated path (see
|
|
||||||
* .Fn MatrixGetJSON
|
|
||||||
* for more information).
|
|
||||||
*/
|
|
||||||
extern JsonValue * MatrixSetJSON(HashMap *, char *, JsonValue *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Traverses the entire JSON object and verifies if no floating-point
|
|
||||||
* values are present within it.
|
|
||||||
*/
|
|
||||||
extern bool MatrixCheckFloats(HashMap *);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -41,23 +41,17 @@
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
|
|
||||||
#include <Schema/RoomCreateRequest.h>
|
#include <Schema/RoomCreateRequest.h>
|
||||||
#include <Schema/PduV1.h>
|
|
||||||
|
|
||||||
#include <Parser.h>
|
|
||||||
#include <User.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The functions in this API operate on an opaque structure.
|
* The functions in this API operate on an opaque structure.
|
||||||
*/
|
*/
|
||||||
typedef struct Room Room;
|
typedef struct Room Room;
|
||||||
|
|
||||||
#include <State.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new room in the given database using the given
|
* Create a new room in the given database using the given
|
||||||
* RoomCreateRequest.
|
* RoomCreateRequest.
|
||||||
*/
|
*/
|
||||||
extern Room * RoomCreate(Db *, User *, RoomCreateRequest *, ServerPart);
|
extern Room * RoomCreate(Db *, RoomCreateRequest *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock the existing room in the specified database,
|
* Lock the existing room in the specified database,
|
||||||
|
@ -69,11 +63,6 @@ extern Room * RoomCreate(Db *, User *, RoomCreateRequest *, ServerPart);
|
||||||
*/
|
*/
|
||||||
extern Room * RoomLock(Db *, char *);
|
extern Room * RoomLock(Db *, char *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the database structure a room is tied to.
|
|
||||||
*/
|
|
||||||
extern Db * RoomGetDB(Room *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock a room handle, returning it to the database.
|
* Unlock a room handle, returning it to the database.
|
||||||
* This function returns the result of calling
|
* This function returns the result of calling
|
||||||
|
@ -99,13 +88,14 @@ extern char * RoomIdGet(Room *);
|
||||||
*/
|
*/
|
||||||
extern int RoomVersionGet(Room *);
|
extern int RoomVersionGet(Room *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the room's state before a specific point,
|
* Resolve the state for the latest events in the
|
||||||
* (with the event hashmap taking priority),
|
* room. This function uses the appropriate state
|
||||||
* like
|
* resolution algorithm to compute the latest state,
|
||||||
* .Fn RoomStateGet .
|
* which is used to select auth events on incoming
|
||||||
|
* client events.
|
||||||
*/
|
*/
|
||||||
extern State * RoomStateGetID(Room *, char *);
|
extern HashMap * RoomStateGet(Room *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of the most recent events in the
|
* Get a list of the most recent events in the
|
||||||
|
@ -136,132 +126,6 @@ extern int RoomPrevEventsSet(Room *, Array *);
|
||||||
* the room version, which includes setting the
|
* the room version, which includes setting the
|
||||||
* prev_events and auth_events fields correctly.
|
* prev_events and auth_events fields correctly.
|
||||||
*/
|
*/
|
||||||
extern HashMap * RoomEventSend(Room *, HashMap *, char **);
|
extern HashMap * RoomEventSend(Room *, HashMap *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an invite to a user in a room, and tries
|
|
||||||
* to notify such user of it.
|
|
||||||
*/
|
|
||||||
extern void RoomSendInvite(User *, bool, char *, Room *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See if a user is allowed to see an event in a room,
|
|
||||||
* based on its visibility.
|
|
||||||
*/
|
|
||||||
extern bool RoomIsEventVisible(Room *, User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch a single event's PDU in a room into an
|
|
||||||
* hashmap, given an event ID, from the database
|
|
||||||
* if possible, or otherwise fetched from a remote
|
|
||||||
* homeserver participating in the room. If the boolean
|
|
||||||
* value is set, then the prev_content is set.
|
|
||||||
*/
|
|
||||||
extern HashMap * RoomEventFetch(Room *, char *, bool);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Behaves like
|
|
||||||
* .Fn RoomEventFetch ,
|
|
||||||
* but avoids doing any postprocessing.
|
|
||||||
*/
|
|
||||||
extern HashMap * RoomEventFetchRaw(Room *, char *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strips all the fields not required in a
|
|
||||||
* Matrix ClientEvent from a PDU object.
|
|
||||||
*/
|
|
||||||
extern HashMap * RoomEventClientify(HashMap *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies whenever an event(as a PDUv1) is
|
|
||||||
* authorised by a room.
|
|
||||||
*/
|
|
||||||
extern bool RoomAuthoriseEventV1(Room *, PduV1, State *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the room's creator as a ServerPart. This value should
|
|
||||||
* not be freed, as it lives alongside the room itself
|
|
||||||
*/
|
|
||||||
extern ServerPart RoomGetCreator(Room *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts a PDUv1 into the event list, while updating the leaf
|
|
||||||
* list.
|
|
||||||
*/
|
|
||||||
extern bool RoomAddEventV1(Room *, PduV1, PduV1Status);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a barebones JSON object to be sent to
|
|
||||||
* .Fn RoomEventFetch .
|
|
||||||
*/
|
|
||||||
extern HashMap * RoomEventCreate(char *, char *, char *, HashMap *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes an approximation of the PDU depth by looking at
|
|
||||||
* the leaves stored in the room data.
|
|
||||||
*/
|
|
||||||
extern uint64_t RoomGetDepth(Room *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to find an alias from room alias to an ID stored
|
|
||||||
* on the heap, or NULL if it does not exist.
|
|
||||||
*/
|
|
||||||
extern char * RoomResolveAlias(Db *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to resolve a list of aliases from a room ID into
|
|
||||||
* an array of strings stored on the heap.
|
|
||||||
*/
|
|
||||||
extern Array * RoomReverseAlias(Db *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees the array returned by
|
|
||||||
* .Fn RoomReverseAlias .
|
|
||||||
*/
|
|
||||||
extern void RoomFreeReverse(Array *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whenever a room contains a specific user.
|
|
||||||
*/
|
|
||||||
extern bool RoomContainsUser(Room *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whenever an user can join a specific room,
|
|
||||||
* given it's permissions.
|
|
||||||
*/
|
|
||||||
extern bool RoomCanJoin(Room *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a local user redact an event(from it's ID).
|
|
||||||
*/
|
|
||||||
extern char * RoomRedact(Room *, User *, char *, char *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a local user join a room, and returns true if
|
|
||||||
* the room was joined.
|
|
||||||
*/
|
|
||||||
extern bool RoomJoin(Room *, User *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a local user leave a room, and returns true if
|
|
||||||
* the room was left.
|
|
||||||
*/
|
|
||||||
extern bool RoomLeave(Room *, User *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds or overwrites a room alias.
|
|
||||||
*/
|
|
||||||
extern void RoomAddAlias(Db *, char *, char *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a PDU has been qualified as 'soft-failed'.
|
|
||||||
*/
|
|
||||||
extern bool RoomIsSoftfailedV1(PduV1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a PDU has been qualified as 'rejected'.
|
|
||||||
*/
|
|
||||||
extern bool RoomIsRejectedV1(PduV1);
|
|
||||||
|
|
||||||
#endif /* TELODENDRIA_ROOM_H */
|
#endif /* TELODENDRIA_ROOM_H */
|
||||||
|
|
|
@ -77,7 +77,6 @@ ROUTE(RouteVersions);
|
||||||
ROUTE(RouteWellKnown);
|
ROUTE(RouteWellKnown);
|
||||||
|
|
||||||
ROUTE(RouteCapabilities);
|
ROUTE(RouteCapabilities);
|
||||||
ROUTE(RouteDevices);
|
|
||||||
ROUTE(RouteLogin);
|
ROUTE(RouteLogin);
|
||||||
ROUTE(RouteLogout);
|
ROUTE(RouteLogout);
|
||||||
ROUTE(RouteRegister);
|
ROUTE(RouteRegister);
|
||||||
|
@ -96,24 +95,12 @@ ROUTE(RouteStaticLogin);
|
||||||
ROUTE(RouteStaticResources);
|
ROUTE(RouteStaticResources);
|
||||||
|
|
||||||
ROUTE(RouteFilter);
|
ROUTE(RouteFilter);
|
||||||
ROUTE(RouteLocalData);
|
|
||||||
|
|
||||||
ROUTE(RouteProcControl);
|
ROUTE(RouteProcControl);
|
||||||
ROUTE(RouteConfig);
|
ROUTE(RouteConfig);
|
||||||
ROUTE(RoutePrivileges);
|
ROUTE(RoutePrivileges);
|
||||||
|
|
||||||
ROUTE(RouteCreateRoom);
|
ROUTE(RouteCreateRoom);
|
||||||
ROUTE(RouteSendEvent);
|
|
||||||
ROUTE(RouteRedact);
|
|
||||||
ROUTE(RouteSendState);
|
|
||||||
ROUTE(RouteJoinRoom);
|
|
||||||
ROUTE(RouteKickRoom);
|
|
||||||
ROUTE(RouteJoinRoomAlias);
|
|
||||||
ROUTE(RouteFetchEvent);
|
|
||||||
ROUTE(RouteJoinedRooms);
|
|
||||||
ROUTE(RouteSync);
|
|
||||||
ROUTE(RouteMessages);
|
|
||||||
ROUTE(RouteMembers);
|
|
||||||
|
|
||||||
ROUTE(RouteAliasDirectory);
|
ROUTE(RouteAliasDirectory);
|
||||||
ROUTE(RouteRoomAliases);
|
ROUTE(RouteRoomAliases);
|
||||||
|
@ -122,9 +109,6 @@ ROUTE(RouteAdminDeactivate);
|
||||||
|
|
||||||
ROUTE(RouteAdminTokens);
|
ROUTE(RouteAdminTokens);
|
||||||
|
|
||||||
ROUTE(RouteKeyQuery);
|
|
||||||
ROUTE(RoutePushrules);
|
|
||||||
ROUTE(RouteHierarchy);
|
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,60 +37,21 @@
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* An opaque structure to hold information about the state.
|
|
||||||
*/
|
|
||||||
typedef struct State State;
|
|
||||||
|
|
||||||
#include <Room.h>
|
#include <Room.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the value of a state tuple.
|
* Retrieve the value of a state tuple.
|
||||||
*/
|
*/
|
||||||
extern char *StateGet(State *, char *, char *);
|
extern char *StateGet(HashMap *, char *, char *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a state tuple to a value.
|
* Set a state tuple to a value.
|
||||||
*/
|
*/
|
||||||
extern void StateSet(State *, char *, char *, char *);
|
extern char *StateSet(HashMap *, char *, char *, char *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterates through a statemap, with (type, key) -> event.
|
|
||||||
* The type and keys are stored on the heap, and will need
|
|
||||||
* to be freed.
|
|
||||||
* This function behaves like
|
|
||||||
* .Fn HashMapIterate .
|
|
||||||
*/
|
|
||||||
extern bool StateIterate(State *, char **, char **, void **);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the room state before the specified event was sent.
|
* Compute the room state before the specified event was sent.
|
||||||
*/
|
*/
|
||||||
extern State * StateResolve(Room *, HashMap *);
|
extern HashMap * StateResolve(Room *, HashMap *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the current state from the room's leaves.
|
|
||||||
*/
|
|
||||||
extern State * StateCurrent(Room *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees an entire state table from the heap.
|
|
||||||
*/
|
|
||||||
extern void StateFree(State *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserialises a state map from JSON to the internal format
|
|
||||||
* used by this API.
|
|
||||||
*
|
|
||||||
* The returned value is independent from the JSON format,
|
|
||||||
* and should be freed with
|
|
||||||
* .Fn StateFree .
|
|
||||||
*/
|
|
||||||
extern State * StateDeserialise(HashMap *);
|
|
||||||
/**
|
|
||||||
* Serialises a state map from the internal format to JSON
|
|
||||||
* used for the database, for example
|
|
||||||
*/
|
|
||||||
extern HashMap * StateSerialise(State *);
|
|
||||||
|
|
||||||
#endif /* TELODENDRIA_STATE_H */
|
#endif /* TELODENDRIA_STATE_H */
|
||||||
|
|
|
@ -44,8 +44,6 @@
|
||||||
|
|
||||||
#include <Parser.h>
|
#include <Parser.h>
|
||||||
|
|
||||||
#include <Schema/KeyUpload.h>
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,8 +66,7 @@ typedef enum UserPrivileges
|
||||||
USER_GRANT_PRIVILEGES = (1 << 3),
|
USER_GRANT_PRIVILEGES = (1 << 3),
|
||||||
USER_PROC_CONTROL = (1 << 4),
|
USER_PROC_CONTROL = (1 << 4),
|
||||||
USER_ALIAS = (1 << 5),
|
USER_ALIAS = (1 << 5),
|
||||||
USER_APPSERVICE = (1 << 6),
|
USER_ALL = ((1 << 6) - 1)
|
||||||
USER_ALL = ((1 << 7) - 1),
|
|
||||||
} UserPrivileges;
|
} UserPrivileges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +91,6 @@ typedef struct UserLoginInfo
|
||||||
char *refreshToken;
|
char *refreshToken;
|
||||||
} UserLoginInfo;
|
} UserLoginInfo;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a localpart and domain as separate parameters and validate them
|
* Take a localpart and domain as separate parameters and validate them
|
||||||
* against the rules of the Matrix specification. The reasion the
|
* against the rules of the Matrix specification. The reasion the
|
||||||
|
@ -138,13 +134,6 @@ extern User * UserCreate(Db *, char *, char *);
|
||||||
*/
|
*/
|
||||||
extern User * UserLock(Db *, char *);
|
extern User * UserLock(Db *, char *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Behaves like
|
|
||||||
* .Fn UserLock ,
|
|
||||||
* but tries to check from a CommonID, and verifies
|
|
||||||
* for the serverpart. */
|
|
||||||
extern User * UserLockID(Db *, CommonID *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take an access token, figure out what user it belongs to, and then
|
* Take an access token, figure out what user it belongs to, and then
|
||||||
* returns a reference to that user. This function should be used by
|
* returns a reference to that user. This function should be used by
|
||||||
|
@ -314,236 +303,9 @@ extern int UserDecodePrivilege(const char *);
|
||||||
*/
|
*/
|
||||||
extern CommonID * UserIdParse(char *, char *);
|
extern CommonID * UserIdParse(char *, char *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the reply sent to the user from a transaction ID, and an opaque
|
|
||||||
* ""path"" ID, which is unique per route.
|
|
||||||
*/
|
|
||||||
extern HashMap * UserGetTransaction(User *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a transaction for a path with a given response JSON.
|
|
||||||
*/
|
|
||||||
extern void UserSetTransaction(User *, char *, char *, HashMap *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts a room (indexed by room ID) in a user's invite list.
|
|
||||||
*/
|
|
||||||
extern void UserAddInvite(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a room from the user's invite list.
|
|
||||||
*/
|
|
||||||
extern void UserRemoveInvite(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all the rooms a user is invited to into an array of
|
|
||||||
* strings(room ID) to be freed by
|
|
||||||
* .Fn UserFreeInvites .
|
|
||||||
*/
|
|
||||||
extern Array * UserListInvites(User *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts a room (indexed by room ID) in a user's join list.
|
|
||||||
*/
|
|
||||||
extern void UserAddJoin(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a room from the user's join list.
|
|
||||||
*/
|
|
||||||
extern void UserRemoveJoin(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all the rooms a user has joined into an array of
|
|
||||||
* strings(room ID) to be freed by
|
|
||||||
* .Fn UserFreeJoins .
|
|
||||||
*/
|
|
||||||
extern Array * UserListJoins(User *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees a join/invite list as created by
|
|
||||||
* .Fn UserListJoins
|
|
||||||
* or
|
|
||||||
* .Fn UserListInvites .
|
|
||||||
*/
|
|
||||||
extern void UserFreeList(Array *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees the user's common ID and the memory allocated for it.
|
* Frees the user's common ID and the memory allocated for it.
|
||||||
*/
|
*/
|
||||||
extern void UserIdFree(CommonID *);
|
extern void UserIdFree(CommonID *);
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialises a sync diff table, and returns a next_batch parameter,
|
|
||||||
* which is stored on the heap.
|
|
||||||
*/
|
|
||||||
extern char * UserInitSyncDiff(User *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fills a sync table with initial sync information
|
|
||||||
*/
|
|
||||||
extern void UserFillSyncDiff(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes an invite onto all sync diff tables.
|
|
||||||
*/
|
|
||||||
extern void UserPushInviteSync(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a join onto all sync diff tables.
|
|
||||||
*/
|
|
||||||
extern void UserPushJoinSync(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes an event (ID) into every diff table of a user.
|
|
||||||
*/
|
|
||||||
extern void UserPushEvent(User *, HashMap *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a global account data key into every diff table
|
|
||||||
* of a user.
|
|
||||||
*/
|
|
||||||
extern void UserPushAccountData(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the invite list(as a room ID table) for an user
|
|
||||||
* and a specified diff table, to be freed by
|
|
||||||
* .Fn UserFreeList .
|
|
||||||
*/
|
|
||||||
extern Array * UserGetInvites(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the global account data list for an user
|
|
||||||
* and a specified diff table, to be freed by
|
|
||||||
* .Fn UserFreeList .
|
|
||||||
*/
|
|
||||||
extern Array * UserGetAccountDataSync(User *, char *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a list of rooms for an user and a specified diff
|
|
||||||
* table, to be freed by
|
|
||||||
* .Fn UserFreeList .
|
|
||||||
*/
|
|
||||||
extern Array * UserGetJoins(User *, char *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of event IDs for a diff table(and room ID),
|
|
||||||
* to be freed by
|
|
||||||
* .Fn UserFreeList .
|
|
||||||
*/
|
|
||||||
extern Array * UserGetEvents(User *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drops a sync diff, denoted by it's previous batch ID, if it
|
|
||||||
* exists.
|
|
||||||
*/
|
|
||||||
extern void UserDropSync(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if the 'sync' data is considered old, and can be
|
|
||||||
* reasonably removed from the database.
|
|
||||||
*/
|
|
||||||
extern bool UserIsSyncOld(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if a sync diff exists.
|
|
||||||
*/
|
|
||||||
extern bool UserSyncExists(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new "message token" start from a room and event ID,
|
|
||||||
* given a specified direction(with false<=>'b' and true<=>'f')
|
|
||||||
* to be used by /messages.
|
|
||||||
*/
|
|
||||||
extern char * UserNewMessageToken(User *, char *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grabs events from a message token(with the correct direction)
|
|
||||||
* into an array of HashMaps(which store PDU objects).
|
|
||||||
* If the string pointer is not NULL, this function sets its
|
|
||||||
* value to a new message token(which is stored on the heap),
|
|
||||||
* if and only if, there are any new events to retrieve.
|
|
||||||
*
|
|
||||||
* If the counter is negative, then it is treated as a 'f',
|
|
||||||
* otherwise, it is treated as 'b'.
|
|
||||||
*/
|
|
||||||
extern Array * UserFetchMessages(User *, int, char *, char **);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a "message token".
|
|
||||||
*/
|
|
||||||
extern void UserFreeMessageToken(User *, char *);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes temporary data about a specific user.
|
|
||||||
*/
|
|
||||||
extern void UserCleanTemporaryData(User *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise a temporary sync table to signal any /sync changes.
|
|
||||||
* This should be paired with a call to
|
|
||||||
* .Fn UserDestroyPushTable .
|
|
||||||
*/
|
|
||||||
extern void UserInitialisePushTable(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the user of a sync update.
|
|
||||||
*/
|
|
||||||
extern void UserNotifyUser(char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies *all* users of a sync update. This is meant to be used
|
|
||||||
* in cases where Telodendria needs to gracefully close any longrunning
|
|
||||||
* /sync requests.
|
|
||||||
*/
|
|
||||||
extern void UserNotifyAll(Db *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for a notification for a specific user for a specific
|
|
||||||
* duration in milliseconds to wait (with 30000ms being the default,
|
|
||||||
* if the value is negative), and returns true if anything came up.
|
|
||||||
*/
|
|
||||||
extern bool UserAwaitNotification(char *, int);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the temporary push table created by
|
|
||||||
* .Fn UserInitialisePushTable .
|
|
||||||
*/
|
|
||||||
extern void UserDestroyPushTable(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a user's account data by key into a JSON object living
|
|
||||||
* on the heap.
|
|
||||||
*/
|
|
||||||
extern HashMap * UserGetAccountData(User *, char *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces an account data entry.
|
|
||||||
*/
|
|
||||||
extern void UserSetAccountData(User *, char *, HashMap *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the device key list.
|
|
||||||
*/
|
|
||||||
extern void UserSetDeviceKeys(User *, DeviceKeys *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the fallback/one-time key list.
|
|
||||||
*/
|
|
||||||
extern void UserClearFallbackKeys(User *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a one-time/fallback key.
|
|
||||||
*/
|
|
||||||
extern void UserAddKey(User *, char *, JsonValue *, bool);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a hashmap from algorithm to one-time key count as
|
|
||||||
* a pointer to the uint64_t.
|
|
||||||
* This is intended for /keys/upload. Please do not use this
|
|
||||||
* elsewhere */
|
|
||||||
extern HashMap * UserGetOnetimeCounts(User *);
|
|
||||||
#endif /* TELODENDRIA_USER_H */
|
#endif /* TELODENDRIA_USER_H */
|
||||||
|
|
Loading…
Reference in a new issue