diff --git a/apps/test/app.js b/apps/test/app.js index 7533113b..2c3166ad 100644 --- a/apps/test/app.js +++ b/apps/test/app.js @@ -1 +1,3 @@ -app.setDocument('

Maybe one day this app will run tests, but for now there is nothing to see here.

'); \ No newline at end of file +app.setDocument( + '

Maybe one day this app will run tests, but for now there is nothing to see here.

' +); diff --git a/core/core.js b/core/core.js index 92c64068..043b74fc 100644 --- a/core/core.js +++ b/core/core.js @@ -851,154 +851,6 @@ function sendData(response, data, type, headers, status_code) { } } -let g_handler_index = 0; - -/** - * TODOC - * @param {*} response - * @param {*} handler_blob_id - * @param {*} path - * @param {*} query - * @param {*} headers - * @param {*} packageOwner - * @param {*} packageName - * @returns - */ -async function useAppHandler( - response, - handler_blob_id, - path, - query, - headers, - packageOwner, - packageName -) { - print('useAppHandler', packageOwner, packageName); - let do_resolve; - let promise = new Promise(async function (resolve, reject) { - do_resolve = resolve; - }); - let process; - let result; - try { - process = await getProcessBlob( - handler_blob_id, - 'handler_' + g_handler_index++, - { - script: 'handler.js', - imports: { - request: { - path: path, - query: query, - }, - respond: do_resolve, - }, - credentials: await httpd.auth_query(headers), - packageOwner: packageOwner, - packageName: packageName, - } - ); - await process.ready; - - result = await promise; - } finally { - if (process?.task) { - await process.task.kill(); - } - } - return result; -} - -/** - * TODOC - * @param {*} request - * @param {*} response - * @param {*} blobId - * @param {*} uri - * @returns - */ -async function blobHandler(request, response, blobId, uri) { - let process; - let data; - let match; - let id; - let app_id = blobId; - let packageOwner; - let packageName; - if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { - packageOwner = match[1]; - packageName = match[2]; - let db = new Database(match[1]); - app_id = await db.get('path:' + match[2]); - } - - let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id))); - id = app_object?.files[uri.substring(1)]; - if (!id && app_object?.files['handler.js']) { - let answer; - try { - answer = await useAppHandler( - response, - app_id, - uri.substring(1), - request.query ? form.decodeForm(request.query) : undefined, - request.headers, - packageOwner, - packageName - ); - } catch (error) { - data = utf8Encode( - `Internal Server Error\n\n${error?.message}\n${error?.stack}` - ); - response.writeHead(500, { - 'Content-Type': 'text/plain; charset=utf-8', - 'Content-Length': data.length, - }); - response.end(data); - return; - } - if (answer && typeof answer.data == 'string') { - answer.data = utf8Encode(answer.data); - } - sendData( - response, - answer?.data, - answer?.content_type, - Object.assign(answer?.headers ?? {}, { - 'Access-Control-Allow-Origin': '*', - 'Content-Security-Policy': k_content_security_policy, - }), - answer.status_code - ); - } else if (id) { - if ( - request.headers['if-none-match'] && - request.headers['if-none-match'] == '"' + id + '"' - ) { - let headers = { - 'Access-Control-Allow-Origin': '*', - 'Content-Security-Policy': k_content_security_policy, - 'Content-Length': '0', - }; - response.writeHead(304, headers); - response.end(); - } else { - let headers = { - ETag: '"' + id + '"', - 'Access-Control-Allow-Origin': '*', - 'Content-Security-Policy': k_content_security_policy, - }; - data = await ssb.blobGet(id); - let type = - httpd.mime_type_from_extension(uri) || - httpd.mime_type_from_magic_bytes(data); - sendData(response, data, type, headers); - } - } else { - sendData(response, data, undefined, {}); - } -} - ssb.addEventListener('message', function () { broadcastEvent('onMessage', [...arguments]); }); @@ -1059,18 +911,6 @@ loadSettings() httpd.set_http_redirect(settings.http_redirect); } httpd.all('/app/socket', app.socket); - httpd.all('/~{word}/{word}/*', function default_http_handler(request, response) { - let match; - if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) { - return blobHandler(request, response, match[1], match[2]); - } - }); - httpd.all('/&*.sha256/*', function default_http_handler(request, response) { - let match; - if ((match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))) { - return blobHandler(request, response, match[1], match[2]); - } - }); let port = httpd.start(tildefriends.http_port); if (tildefriends.args.out_http_port_file) { print('Writing the port file.'); diff --git a/src/httpd.js.c b/src/httpd.js.c index 40f2b0aa..62ccc5ee 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -1034,6 +1034,131 @@ static user_app_t* _parse_user_app_from_path(const char* path, const char* expec return result; } +typedef struct _app_blob_t +{ + tf_http_request_t* request; + bool found; + bool not_modified; + void* data; + size_t size; + char etag[256]; +} app_blob_t; + +static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) +{ + app_blob_t* data = user_data; + tf_http_request_t* request = data->request; + char app_id[256] = ""; + const char* file = NULL; + if (request->path[0] == '/' && request->path[1] == '~') + { + const char* last_slash = strchr(request->path + 1, '/'); + if (last_slash) + { + last_slash = strchr(last_slash + 1, '/'); + } + user_app_t* user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL; + if (user_app) + { + 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* value = tf_ssb_db_get_property(ssb, user_app->user, app_path); + snprintf(app_id, sizeof(app_id), "%s", value); + tf_free(app_path); + tf_free((void*)value); + file = last_slash + 1; + tf_free(user_app); + } + } + else if (request->path[0] == '/' && request->path[1] == '&') + { + const char* end = strstr(request->path, ".sha256/"); + if (end) + { + snprintf(app_id, sizeof(app_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1); + file = end + strlen(".sha256/"); + } + } + + char* app_blob = NULL; + size_t app_blob_size = 0; + if (*app_id && tf_ssb_db_blob_get(ssb, app_id, (uint8_t**)&app_blob, &app_blob_size)) + { + JSMallocFunctions funcs = { 0 }; + tf_get_js_malloc_functions(&funcs); + JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL); + JSContext* context = JS_NewContext(runtime); + + JSValue app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL); + JSValue files = JS_GetPropertyStr(context, app_object, "files"); + JSValue blob_id = JS_GetPropertyStr(context, files, file); + const char* blob_id_str = JS_ToCString(context, blob_id); + if (blob_id_str) + { + snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str); + const char* match = tf_http_request_get_header(data->request, "if-none-match"); + if (match && strcmp(match, data->etag) == 0) + { + data->not_modified = true; + } + else + { + data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size); + } + } + JS_FreeCString(context, blob_id_str); + JS_FreeValue(context, blob_id); + JS_FreeValue(context, files); + JS_FreeValue(context, app_object); + + JS_FreeContext(context); + JS_FreeRuntime(runtime); + tf_free(app_blob); + } +} + +static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + app_blob_t* data = user_data; + if (data->not_modified) + { + tf_http_respond(data->request, 304, NULL, 0, NULL, 0); + } + else if (data->found) + { + const char* mime_type = _ext_to_content_type(strrchr(data->request->path, '.'), false); + if (!mime_type) + { + mime_type = _httpd_mime_type_from_magic_bytes_internal(data->data, data->size); + } + const char* headers[] = { + "Access-Control-Allow-Origin", + "*", + "Content-Security-Policy", + "sandbox allow-downloads allow-top-navigation-by-user-activation", + "Content-Type", + mime_type ? mime_type : "application/binary", + "etag", + data->etag, + }; + tf_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size); + } + tf_free(data->data); + tf_http_request_unref(data->request); + tf_free(data); +} + +static void _httpd_endpoint_app_blob(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); + app_blob_t* data = tf_malloc(sizeof(app_blob_t)); + *data = (app_blob_t) { .request = request }; + tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data); +} + typedef struct _view_t { tf_http_request_t* request; @@ -2117,14 +2242,17 @@ void tf_httpd_register(JSContext* context) tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task); + tf_http_add_handler(http, "/&*.sha256", _httpd_endpoint_add_slash, NULL, task); tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task); - tf_http_add_handler(http, "/*/view", _httpd_endpoint_view, NULL, task); + tf_http_add_handler(http, "/&*.sha256/view", _httpd_endpoint_view, NULL, task); + tf_http_add_handler(http, "/&*.sha256/*", _httpd_endpoint_app_blob, NULL, task); + tf_http_add_handler(http, "/~{word}/{word}", _httpd_endpoint_add_slash, NULL, task); tf_http_add_handler(http, "/~{word}/{word}/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/~{word}/{word}/save", _httpd_endpoint_save, NULL, task); tf_http_add_handler(http, "/~{word}/{word}/delete", _httpd_endpoint_delete, NULL, task); + tf_http_add_handler(http, "/~{word}/{word}/view", _httpd_endpoint_view, NULL, task); + tf_http_add_handler(http, "/~{word}/{word}/*", _httpd_endpoint_app_blob, NULL, task); tf_http_add_handler(http, "/save", _httpd_endpoint_save, NULL, task); - tf_http_add_handler(http, "/~{word}/{word}", _httpd_endpoint_add_slash, NULL, task); - tf_http_add_handler(http, "/&*.sha256", _httpd_endpoint_add_slash, NULL, task); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);