diff --git a/src/Matrix.c b/src/Matrix.c index e66efdd..3a25b5c 100644 --- a/src/Matrix.c +++ b/src/Matrix.c @@ -509,3 +509,32 @@ MatrixSetJSON(HashMap *json, char *string, JsonValue *new) } 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; +} + diff --git a/src/Room.c b/src/Room.c index a7781dc..8a1dc2a 100644 --- a/src/Room.c +++ b/src/Room.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -239,6 +240,14 @@ RoomEventSend(Room * room, HashMap * event, char **errp) { return NULL; } + if (!MatrixCheckFloats(event)) + { + if (errp) + { + *errp = "Event contains forbidden float value"; + } + return NULL; + } if (room->version < 3) { /* Manage with PDUv1 */ diff --git a/src/Room/State.c b/src/Room/State.c index d50efc2..c34f58f 100644 --- a/src/Room/State.c +++ b/src/Room/State.c @@ -5,22 +5,6 @@ #include #include -State * -RoomStateGet(Room * room) -{ - HashMap *database_state; - if (!room) - { - return NULL; - } - - /* TODO: Consider caching the deserialised result, as doing that on a - * large state would probably eat up a lot of time! */ - - /* TODO: Update the cached state */ - database_state = DbJson(room->state_ref); - return StateDeserialise(database_state); -} State * RoomStateGetID(Room * room, char *event_id) { diff --git a/src/Routes.c b/src/Routes.c index 01d893a..1e98522 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -85,6 +85,7 @@ RouterBuild(void) 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); diff --git a/src/Routes/RouteMembers.c b/src/Routes/RouteMembers.c new file mode 100644 index 0000000..6cfb471 --- /dev/null +++ b/src/Routes/RouteMembers.c @@ -0,0 +1,175 @@ +/* + * 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 + + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +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)), 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; +} diff --git a/src/Routes/RouteSendEvent.c b/src/Routes/RouteSendEvent.c index b10bd9d..28a6c30 100644 --- a/src/Routes/RouteSendEvent.c +++ b/src/Routes/RouteSendEvent.c @@ -173,6 +173,7 @@ ROUTE_IMPL(RouteSendState, path, argp) char *sender = NULL; Room *room = NULL; + State *state = NULL; char *err; @@ -182,20 +183,10 @@ ROUTE_IMPL(RouteSendState, path, argp) 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) + if (!stateKey) { - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, NULL); - goto finish; + stateKey = ""; } response = MatrixGetAccessToken(args->context, &token); @@ -203,15 +194,6 @@ ROUTE_IMPL(RouteSendState, path, argp) { 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) { @@ -219,40 +201,94 @@ ROUTE_IMPL(RouteSendState, path, argp) 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); - room = RoomLock(db, roomId); - if (!RoomContainsUser(room, sender)) + switch (HttpRequestMethodGet(args->context)) { - err = "User is not in the room."; - HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); - response = MatrixErrorCreate(M_FORBIDDEN, err); - goto finish; + 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)); + 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) + ); + 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); + break; + default: + err = "Unknown request method."; + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED, err); + goto finish; } - - event = RoomEventCreate( - sender, - eventType, stateKey ? stateKey : "", - JsonDuplicate(request) - ); - 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); finish: RoomUnlock(room); Free(serverName); diff --git a/src/include/Matrix.h b/src/include/Matrix.h index 0a7532d..34bbbb3 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -170,4 +170,10 @@ extern JsonValue * MatrixGetJSON(HashMap *, char *); */ 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 diff --git a/src/include/Room.h b/src/include/Room.h index 24c6e0d..96321b7 100644 --- a/src/include/Room.h +++ b/src/include/Room.h @@ -99,14 +99,6 @@ extern char * RoomIdGet(Room *); */ extern int RoomVersionGet(Room *); -/** - * Resolve the state for the latest events in the - * room. This function uses the appropriate state - * resolution algorithm to compute the latest state, - * which is used to select auth events on incoming - * client events. - */ -extern State * RoomStateGet(Room *); /** * Resolves the room's state before a specific point, * (with the event hashmap taking priority), diff --git a/src/include/Routes.h b/src/include/Routes.h index 0300266..5546917 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -111,6 +111,7 @@ ROUTE(RouteFetchEvent); ROUTE(RouteJoinedRooms); ROUTE(RouteSync); ROUTE(RouteMessages); +ROUTE(RouteMembers); ROUTE(RouteAliasDirectory); ROUTE(RouteRoomAliases);