Implement filter validation by using j2s.

This commit is contained in:
Jordan Bancino 2023-08-05 13:46:23 +00:00
parent 77d71989df
commit 5f3220372e
7 changed files with 169 additions and 112 deletions

View file

@ -4,3 +4,5 @@ data
*.patch *.patch
*.log *.log
vgcore.* vgcore.*
src/Schema
src/include/Schema

121
Schema/Filter.json Normal file
View file

@ -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"
}

View file

@ -31,16 +31,12 @@ Milestone: v0.4.0
[ ] Registration token endpoints [ ] Registration token endpoints
[~] Cytoplasm [~] 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 [~] Debug OpenSSL
[x] Database corruption [x] Database corruption
[ ] File descriptor exhaustion [ ] File descriptor exhaustion
[ ] Random memory corruption after many requests. [ ] Random memory corruption after many requests.
[ ] j2s [~] j2s
[x] Properly support arrays of primitives.
[ ] How to set default value of true on boolean? [ ] How to set default value of true on boolean?
- defaults probably need to be set at parser level, not - defaults probably need to be set at parser level, not
enough to set it after. enough to set it after.
@ -49,8 +45,6 @@ Milestone: v0.4.0
use the default. This will make debugging a lot easier. use the default. This will make debugging a lot easier.
[ ] Make sure admin registration token is printed to log, not stdout. [ ] Make sure admin registration token is printed to log, not stdout.
Unless they are the same, of course. Unless they are the same, of course.
[x] Fix Json UTF-8 handling.
[ ] Update Makefile
Milestone: v0.5.0 Milestone: v0.5.0
----------------- -----------------

View file

@ -21,92 +21,12 @@
* 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 <Routes.h> #include <Filter.h>
#include <string.h> #include <Schema/Filter.h>
#include <HashMap.h>
#include <Memory.h>
#include <User.h>
#include <Json.h>
#include <Str.h>
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 * HashMap *
FilterValidate(HashMap * json) FilterApply(Filter *filter, HashMap *event)
{ {
JsonValue *val; return NULL;
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;
} }

View file

@ -30,10 +30,10 @@
#include <Str.h> #include <Str.h>
#include <User.h> #include <User.h>
#include <Filter.h>
#include <string.h> #include <string.h>
#include <Schema/Filter.h>
static char * static char *
GetServerName(Db * db) GetServerName(Db * db)
{ {
@ -139,6 +139,10 @@ ROUTE_IMPL(RouteFilter, path, argp)
DbRef *ref; DbRef *ref;
char *filterId; char *filterId;
Filter filter = { 0 };
char *parseErr;
HashMap *filterJson;
request = JsonDecode(HttpServerStream(args->context)); request = JsonDecode(HttpServerStream(args->context));
if (!request) if (!request)
{ {
@ -147,12 +151,14 @@ ROUTE_IMPL(RouteFilter, path, argp)
goto finish; goto finish;
} }
response = FilterValidate(request); if (!FilterFromJson(request, &filter, &parseErr))
if (response)
{ {
/* TODO: send parseErr to client */
response = MatrixErrorCreate(M_BAD_JSON);
goto finish; goto finish;
} }
filterId = StrRandom(12); filterId = StrRandom(12);
if (!filterId) if (!filterId)
{ {
@ -170,12 +176,17 @@ ROUTE_IMPL(RouteFilter, path, argp)
goto finish; goto finish;
} }
DbJsonSet(ref, request); filterJson = FilterToJson(&filter);
DbJsonSet(ref, filterJson);
DbUnlock(db, ref); DbUnlock(db, ref);
Free(filterJson);
response = HashMapCreate(); response = HashMapCreate();
HashMapSet(response, "filter_id", JsonValueString(filterId)); HashMapSet(response, "filter_id", JsonValueString(filterId));
Free(filterId); Free(filterId);
FilterFree(&filter);
} }
else else
{ {

View file

@ -24,6 +24,8 @@
#ifndef TELODENDRIA_FILTER_H #ifndef TELODENDRIA_FILTER_H
#define TELODENDRIA_FILTER_H #define TELODENDRIA_FILTER_H
#include <Schema/Filter.h>
/*** /***
* @Nm Filter * @Nm Filter
* @Nd Validate JSON filters and apply them to events. * @Nd Validate JSON filters and apply them to events.
@ -35,22 +37,11 @@
*/ */
/** /**
* Validate a JSON filter as provided by a client. This function * Apply the given filter to the given event, returning a
* 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 * new event JSON with the filter applied, or NULL if the event
* is excluded totally by the rules of the filter. * is excluded totally by the rules of the filter.
*/ */
extern HashMap * extern HashMap *
FilterEvent(HashMap *, HashMap *); FilterApply(Filter *, HashMap *);
#endif /* TELODENDRIA_FILTER_H */ #endif /* TELODENDRIA_FILTER_H */

View file

@ -105,7 +105,7 @@ setsubst() {
recipe_docs() { recipe_docs() {
mkdir -p build/man/man3 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") basename=$(basename "$header")
man=$(echo "build/man/man3/$basename" | sed -e 's/\.h$/\.3/') man=$(echo "build/man/man3/$basename" | sed -e 's/\.h$/\.3/')
@ -134,10 +134,28 @@ recipe_cytoplasm() {
cd - > /dev/null 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' # Build the source code, and generate the 'build/telodendria'
# binary. # binary.
recipe_build() { recipe_build() {
recipe_cytoplasm recipe_schema
cd src cd src
mkdir -p ../build mkdir -p ../build