From 5f3220372e0b02741e99fd34930e36186888f1b3 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sat, 5 Aug 2023 13:46:23 +0000 Subject: [PATCH] Implement filter validation by using j2s. --- .cvsignore | 2 + Schema/Filter.json | 121 +++++++++++++++++++++++++++++++++++++++ TODO.txt | 10 +--- src/Filter.c | 88 ++-------------------------- src/Routes/RouteFilter.c | 21 +++++-- src/include/Filter.h | 17 ++---- tools/bin/td | 22 ++++++- 7 files changed, 169 insertions(+), 112 deletions(-) create mode 100644 Schema/Filter.json diff --git a/.cvsignore b/.cvsignore index 6cab8d0..688cac8 100644 --- a/.cvsignore +++ b/.cvsignore @@ -4,3 +4,5 @@ data *.patch *.log vgcore.* +src/Schema +src/include/Schema diff --git a/Schema/Filter.json b/Schema/Filter.json new file mode 100644 index 0000000..ee6a233 --- /dev/null +++ b/Schema/Filter.json @@ -0,0 +1,121 @@ +{ + "header": "Schema\/Filter.h", + "types": { + "FilterRoom": { + "fields": { + "not_rooms": { + "type": "[string]" + }, + "state": { + "type": "FilterRoomEvent" + }, + "include_leave": { + "type": "boolean" + }, + "timeline": { + "type": "FilterRoomEvent" + }, + "account_data": { + "type": "FilterRoomEvent" + }, + "rooms": { + "type": "[string]" + }, + "ephemeral": { + "type": "FilterRoomEvent" + } + }, + "type": "struct" + }, + "FilterEventFormat": { + "fields": { + "federation": { + "name": "FILTER_FORMAT_FEDERATION" + }, + "client": { + "name": "FILTER_FORMANT_CLIENT" + } + }, + "type": "enum" + }, + "FilterEvent": { + "fields": { + "not_senders": { + "type": "[string]" + }, + "limit": { + "type": "integer" + }, + "senders": { + "type": "[string]" + }, + "types": { + "type": "[string]" + }, + "not_types": { + "type": "[string]" + } + }, + "type": "struct" + }, + "Filter": { + "fields": { + "event_format": { + "type": "FilterEventFormat" + }, + "presence": { + "type": "FilterEvent" + }, + "account_data": { + "type": "FilterEvent" + }, + "room": { + "type": "FilterRoom" + }, + "event_fields": { + "type": "[string]" + } + }, + "type": "struct" + }, + "FilterRoomEvent": { + "fields": { + "not_rooms": { + "type": "[string]" + }, + "not_senders": { + "type": "[string]" + }, + "limit": { + "type": "integer" + }, + "senders": { + "type": "[string]" + }, + "include_redundant_members": { + "type": "boolean" + }, + "types": { + "type": "[string]" + }, + "rooms": { + "type": "[string]" + }, + "lazy_load_members": { + "type": "boolean" + }, + "not_types": { + "type": "[string]" + }, + "contains_url": { + "type": "boolean" + }, + "unread_thread_notifications": { + "type": "boolean" + } + }, + "type": "struct" + } + }, + "guard": "TELODENDRIA_SCHEMA_FILTER_H" +} diff --git a/TODO.txt b/TODO.txt index e26784e..43a7ba9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -31,16 +31,12 @@ Milestone: v0.4.0 [ ] Registration token endpoints [~] Cytoplasm - [ ] HashMap/Json - [ ] Strip all keys except subset - [ ] Strip only subset of keys - [ ] Object validation - contains only certain keys, - with certain types, no extraneous keys, etc. [~] Debug OpenSSL [x] Database corruption [ ] File descriptor exhaustion [ ] Random memory corruption after many requests. -[ ] j2s +[~] j2s + [x] Properly support arrays of primitives. [ ] How to set default value of true on boolean? - defaults probably need to be set at parser level, not enough to set it after. @@ -49,8 +45,6 @@ Milestone: v0.4.0 use the default. 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. -[x] Fix Json UTF-8 handling. -[ ] Update Makefile Milestone: v0.5.0 ----------------- diff --git a/src/Filter.c b/src/Filter.c index 0f6ad56..2527e51 100644 --- a/src/Filter.c +++ b/src/Filter.c @@ -21,92 +21,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#include +#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; -} +#include HashMap * -FilterValidate(HashMap * json) +FilterApply(Filter *filter, HashMap *event) { - 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; + return NULL; } diff --git a/src/Routes/RouteFilter.c b/src/Routes/RouteFilter.c index 02c6f60..aeee5e6 100644 --- a/src/Routes/RouteFilter.c +++ b/src/Routes/RouteFilter.c @@ -30,10 +30,10 @@ #include #include -#include - #include +#include + static char * GetServerName(Db * db) { @@ -139,6 +139,10 @@ ROUTE_IMPL(RouteFilter, path, argp) DbRef *ref; char *filterId; + Filter filter = { 0 }; + char *parseErr; + HashMap *filterJson; + request = JsonDecode(HttpServerStream(args->context)); if (!request) { @@ -147,12 +151,14 @@ ROUTE_IMPL(RouteFilter, path, argp) goto finish; } - response = FilterValidate(request); - if (response) + if (!FilterFromJson(request, &filter, &parseErr)) { + /* TODO: send parseErr to client */ + response = MatrixErrorCreate(M_BAD_JSON); goto finish; } + filterId = StrRandom(12); if (!filterId) { @@ -170,12 +176,17 @@ ROUTE_IMPL(RouteFilter, path, argp) goto finish; } - DbJsonSet(ref, request); + filterJson = FilterToJson(&filter); + DbJsonSet(ref, filterJson); DbUnlock(db, ref); + Free(filterJson); + response = HashMapCreate(); HashMapSet(response, "filter_id", JsonValueString(filterId)); Free(filterId); + + FilterFree(&filter); } else { diff --git a/src/include/Filter.h b/src/include/Filter.h index 5328ffd..46c5131 100644 --- a/src/include/Filter.h +++ b/src/include/Filter.h @@ -24,6 +24,8 @@ #ifndef TELODENDRIA_FILTER_H #define TELODENDRIA_FILTER_H +#include + /*** * @Nm Filter * @Nd Validate JSON filters and apply them to events. @@ -35,22 +37,11 @@ */ /** - * 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 + * Apply the given 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 *); +FilterApply(Filter *, HashMap *); #endif /* TELODENDRIA_FILTER_H */ diff --git a/tools/bin/td b/tools/bin/td index 3f17520..a792b54 100644 --- a/tools/bin/td +++ b/tools/bin/td @@ -105,7 +105,7 @@ setsubst() { recipe_docs() { mkdir -p build/man/man3 - for header in $(find src -name '*.h'); do + for header in $(find src -name '*.h' -not -path '*Schema*'); do basename=$(basename "$header") man=$(echo "build/man/man3/$basename" | sed -e 's/\.h$/\.3/') @@ -134,10 +134,28 @@ recipe_cytoplasm() { cd - > /dev/null } +recipe_schema() { + recipe_cytoplasm + + mkdir -p "src/Schema" "src/include/Schema" + + find Schema -name '*.json' | while IFS= read -r schema; do + header="src/include/Schema/$(basename $schema .json).h" + impl="src/Schema/$(basename $schema .json).c" + + mod=$(mod_time $schema) + + if [ $mod -gt $(mod_time $header) ] || [ $mod -gt $(mod_time $impl) ]; then + echo "J2S $(basename $schema .json)" + j2s -s "$schema" -h "$header" -c "$impl" + fi + done +} + # Build the source code, and generate the 'build/telodendria' # binary. recipe_build() { - recipe_cytoplasm + recipe_schema cd src mkdir -p ../build