#include "httpd.js.h" #include "http.h" #include "log.h" #include "mem.h" #include "ssb.db.h" #include "ssb.h" #include "task.h" #include "util.js.h" typedef struct _save_t { tf_http_request_t* request; int response; char blob_id[k_blob_id_len]; } save_t; static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data) { save_t* save = user_data; tf_http_request_t* request = save->request; const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); JSMallocFunctions funcs = { 0 }; tf_get_js_malloc_functions(&funcs); JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL); JSContext* context = JS_NewContext(runtime); JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session); JSValue user = JS_GetPropertyStr(context, jwt, "name"); const char* user_string = JS_ToCString(context, user); if (user_string && tf_httpd_is_name_valid(user_string)) { tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/save"); if (user_app) { if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration"))) { size_t path_length = strlen("path:") + strlen(user_app->app) + 1; char* app_path = tf_malloc(path_length); snprintf(app_path, path_length, "path:%s", user_app->app); const char* old_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path); JSValue new_app = JS_ParseJSON(context, request->body, request->content_length, NULL); tf_util_report_error(context, new_app); if (JS_IsObject(new_app)) { uint8_t* old_blob = NULL; size_t old_blob_size = 0; if (tf_ssb_db_blob_get(ssb, old_blob_id, &old_blob, &old_blob_size)) { JSValue old_app = JS_ParseJSON(context, (const char*)old_blob, old_blob_size, NULL); if (JS_IsObject(old_app)) { JSAtom previous = JS_NewAtom(context, "previous"); JS_DeleteProperty(context, old_app, previous, 0); JS_DeleteProperty(context, new_app, previous, 0); JSValue old_app_json = JS_JSONStringify(context, old_app, JS_NULL, JS_NULL); JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL); const char* old_app_str = JS_ToCString(context, old_app_json); const char* new_app_str = JS_ToCString(context, new_app_json); if (old_app_str && new_app_str && strcmp(old_app_str, new_app_str) == 0) { snprintf(save->blob_id, sizeof(save->blob_id), "/%s", old_blob_id); save->response = 200; } JS_FreeCString(context, old_app_str); JS_FreeCString(context, new_app_str); JS_FreeValue(context, old_app_json); JS_FreeValue(context, new_app_json); JS_FreeAtom(context, previous); } JS_FreeValue(context, old_app); tf_free(old_blob); } if (!save->response) { if (old_blob_id) { JS_SetPropertyStr(context, new_app, "previous", JS_NewString(context, old_blob_id)); } JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL); size_t new_app_length = 0; const char* new_app_str = JS_ToCStringLen(context, &new_app_length, new_app_json); char blob_id[k_blob_id_len] = { 0 }; if (tf_ssb_db_blob_store(ssb, (const uint8_t*)new_app_str, new_app_length, blob_id, sizeof(blob_id), NULL) && tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id)) { tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app); tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id); save->response = 200; } else { tf_printf("Blob store or property set failed.\n"); save->response = 500; } JS_FreeCString(context, new_app_str); JS_FreeValue(context, new_app_json); } } else { save->response = 400; } JS_FreeValue(context, new_app); tf_free(app_path); tf_free((void*)old_blob_id); } else { save->response = 403; } tf_free(user_app); } else if (strcmp(request->path, "/save") == 0) { char blob_id[k_blob_id_len] = { 0 }; if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL)) { tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id); save->response = 200; } else { tf_printf("Blob store failed.\n"); save->response = 500; } } else { save->response = 400; } } else { save->response = 401; } tf_free((void*)session); JS_FreeCString(context, user_string); JS_FreeValue(context, user); JS_FreeValue(context, jwt); JS_FreeContext(context); JS_FreeRuntime(runtime); } static void _httpd_endpoint_save_after_work(tf_ssb_t* ssb, int status, void* user_data) { save_t* save = user_data; tf_http_request_t* request = save->request; if (*save->blob_id) { char body[256] = ""; int length = snprintf(body, sizeof(body), "/%s", save->blob_id); tf_http_respond(request, 200, NULL, 0, body, length); } tf_http_request_unref(request); tf_free(save); } void tf_httpd_endpoint_save(tf_http_request_t* request) { tf_http_request_ref(request); tf_task_t* task = request->user_data; tf_ssb_t* ssb = tf_task_get_ssb(task); save_t* save = tf_malloc(sizeof(save_t)); *save = (save_t) { .request = request, }; tf_ssb_run_work(ssb, _httpd_endpoint_save_work, _httpd_endpoint_save_after_work, save); }