From 861d4146c0fd98a26da904d2d279e94afc7d5914 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sun, 18 Jun 2023 02:53:06 +0000 Subject: [PATCH] Add Filter API to validate filters and process events. --- TODO.txt | 2 + src/Filter.c | 112 +++++++++++++++++++++++++++++++++++++++ src/Routes/RouteFilter.c | 65 +++++++++++++++++++---- src/include/Filter.h | 56 ++++++++++++++++++++ 4 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 src/Filter.c create mode 100644 src/include/Filter.h diff --git a/TODO.txt b/TODO.txt index 39067bc..e78290d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -34,6 +34,8 @@ Milestone: v0.4.0 [ ] Refactor MatrixErrorCreate() to take a custom message. This will make debugging a lot easier. +[ ] Make sure admin registration token is printed to log, not stdout. + Unless they are the same, of course. Milestone: v0.5.0 ----------------- diff --git a/src/Filter.c b/src/Filter.c new file mode 100644 index 0000000..8d02104 --- /dev/null +++ b/src/Filter.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 + +static HashMap * +ValidateRoomFilter(HashMap *json) +{ + return NULL; +} + +static HashMap * +ValidateEventFields(Array *fields) +{ + return NULL; +} + +static HashMap * +ValidateEventFormat(char *fmt) +{ + return NULL; +} + +static HashMap * +ValidateEventFilter(HashMap *json) +{ + JsonValue *val; + + val = HashMapGet(json, "limit"); + if (val) + { + if (JsonValueType(val) == JSON_INTEGER) + { + long limit = JsonValueAsInteger(val); + if (limit <= 0 || limit > 100) + { + return MatrixErrorCreate(M_BAD_JSON); + } + } + else + { + return MatrixErrorCreate(M_BAD_JSON); + } + } + + return NULL; +} + +HashMap * +FilterValidate(HashMap *json) +{ + JsonValue *val; + HashMap *response = NULL; + +#define VALIDATE(key, type, func, param) \ + val = HashMapGet(json, key); \ + if (val) \ + { \ + if (JsonValueType(val) == type) \ + { \ + response = func(param); \ + if (response) \ + { \ + goto finish; \ + } \ + } \ + else \ + { \ + return MatrixErrorCreate(M_BAD_JSON); \ + } \ + } + + VALIDATE("account_data", JSON_OBJECT, ValidateEventFilter, JsonValueAsObject(val)); + VALIDATE("event_fields", JSON_ARRAY, ValidateEventFields, JsonValueAsArray(val)); + VALIDATE("event_format", JSON_STRING, ValidateEventFormat, JsonValueAsString(val)); + VALIDATE("presence", JSON_OBJECT, ValidateEventFilter, JsonValueAsObject(val)); + VALIDATE("room", JSON_OBJECT, ValidateRoomFilter, JsonValueAsObject(val)); + +#undef VALIDATE + +finish: + return response; +} + diff --git a/src/Routes/RouteFilter.c b/src/Routes/RouteFilter.c index 7c47a86..0d1e67d 100644 --- a/src/Routes/RouteFilter.c +++ b/src/Routes/RouteFilter.c @@ -23,14 +23,17 @@ */ #include -#include #include #include -#include #include #include +#include +#include + +#include + static char * GetServerName(Db *db) { @@ -55,6 +58,7 @@ ROUTE_IMPL(RouteFilter, path, argp) RouteArgs *args = argp; Db *db = args->matrixArgs->db; + HashMap *request = NULL; HashMap *response = NULL; User *user = NULL; @@ -101,13 +105,6 @@ ROUTE_IMPL(RouteFilter, path, argp) goto finish; } - if (!StrEquals(id->localpart, UserGetName(user))) - { - HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); - response = MatrixErrorCreate(M_INVALID_PARAM); - goto finish; - } - user = UserAuthenticate(db, token); if (!user) { @@ -116,6 +113,13 @@ ROUTE_IMPL(RouteFilter, path, argp) goto finish; } + if (!StrEquals(id->localpart, UserGetName(user))) + { + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_INVALID_PARAM); + goto finish; + } + if (ArraySize(path) == 2 && HttpRequestMethodGet(args->context) == HTTP_GET) { DbRef *ref = DbLock(db, 3, "filters", UserGetName(user), ArrayGet(path, 1)); @@ -132,8 +136,46 @@ ROUTE_IMPL(RouteFilter, path, argp) } else if (ArraySize(path) == 1 && HttpRequestMethodGet(args->context) == HTTP_POST) { - /* TODO */ - response = MatrixErrorCreate(M_UNKNOWN); + DbRef *ref; + char *filterId; + + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_NOT_JSON); + goto finish; + } + + response = FilterValidate(request); + if (response) + { + goto finish; + } + + filterId = StrRandom(12); + if (!filterId) + { + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN); + goto finish; + } + + ref = DbCreate(db, 3, "filters", UserGetName(user), filterId); + if (!ref) + { + Free(filterId); + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN); + goto finish; + } + + DbJsonSet(ref, request); + DbUnlock(db, ref); + + response = HashMapCreate(); + HashMapSet(response, "filter_id", JsonValueString(filterId)); + Free(filterId); } else { @@ -145,5 +187,6 @@ finish: Free(serverName); UserIdFree(id); UserUnlock(user); + JsonFree(request); return response; } diff --git a/src/include/Filter.h b/src/include/Filter.h new file mode 100644 index 0000000..5328ffd --- /dev/null +++ b/src/include/Filter.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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. + */ +#ifndef TELODENDRIA_FILTER_H +#define TELODENDRIA_FILTER_H + +/*** + * @Nm Filter + * @Nd Validate JSON filters and apply them to events. + * @Dd June 17 2023 + * + * The Matrix Client-Server API defines a mechanism for defining + * filters and applying them to certain endpoints. This API allows + * those filters to be validated and applied to events. + */ + +/** + * Validate a JSON filter as provided by a client. This function + * takes in a filter and returns a JSON response if a validation + * error occurs. If an error occurs, the response should be returned + * to the client. If no error occurs, then NULL will be returned and + * the caller may proceed with the assumption that the filter is + * valid. + */ +extern HashMap * +FilterValidate(HashMap *); + +/** + * Apply the given JSON filter to the given event, returning a + * new event JSON with the filter applied, or NULL if the event + * is excluded totally by the rules of the filter. + */ +extern HashMap * +FilterEvent(HashMap *, HashMap *); + +#endif /* TELODENDRIA_FILTER_H */