core: Make it possible to host a web page with no additional server-side JS. An experiment in supporting simplicity and increased ability to be publicly searched and archived.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m55s

This commit is contained in:
2025-07-16 12:47:16 -04:00
parent a8f9b67f71
commit 5edfe732b1
2 changed files with 213 additions and 8 deletions

View File

@@ -1050,6 +1050,8 @@ function save(save_to) {
if (save_path != window.location.pathname) {
alert('Saved to ' + save_path + '.');
} else if (!gFiles['app.js']) {
window.location.reload();
} else {
reconnect(save_path);
}

View File

@@ -596,13 +596,8 @@ static void _httpd_endpoint_mem(tf_http_request_t* request)
static const char* _after(const char* text, const char* prefix)
{
if (!text || !prefix)
{
return NULL;
}
size_t prefix_length = strlen(prefix);
if (strncmp(text, prefix, prefix_length) == 0)
if (text && strncmp(text, prefix, prefix_length) == 0)
{
return text + prefix_length;
}
@@ -703,7 +698,7 @@ static void _httpd_endpoint_static_stat(tf_task_t* task, const char* path, int r
static void _httpd_endpoint_static(tf_http_request_t* request)
{
if (strncmp(request->path, "/.well-known/", strlen("/.well-known/")) && _httpd_redirect(request))
if (request->path && strncmp(request->path, "/.well-known/", strlen("/.well-known/")) && _httpd_redirect(request))
{
return;
}
@@ -859,6 +854,7 @@ typedef struct _app_blob_t
bool found;
bool not_modified;
bool use_handler;
bool use_static;
void* data;
size_t size;
char app_blob_id[k_blob_id_len];
@@ -1005,6 +1001,10 @@ static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void*
{
tf_http_respond(data->request, 304, NULL, 0, NULL, 0);
}
else if (data->use_static)
{
_httpd_endpoint_static(data->request);
}
else if (data->use_handler)
{
_httpd_call_app_handler(ssb, data->request, data->app_blob_id, data->file, data->user_app->user, data->user_app->app);
@@ -1044,6 +1044,209 @@ static void _httpd_endpoint_app_blob(tf_http_request_t* request)
tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data);
}
static bool _has_property(JSContext* context, JSValue object, const char* name)
{
JSAtom atom = JS_NewAtom(context, name);
bool result = JS_HasProperty(context, object, atom) > 0;
JS_FreeAtom(context, atom);
return result;
}
static void _httpd_endpoint_app_index_work(tf_ssb_t* ssb, void* user_data)
{
app_blob_t* data = user_data;
data->use_static = true;
user_app_t* user_app = data->user_app;
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
char* app_path = tf_malloc(app_path_length);
snprintf(app_path, app_path_length, "path:%s", user_app->app);
const char* app_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
tf_free(app_path);
uint8_t* app_blob = NULL;
size_t app_blob_size = 0;
if (tf_ssb_db_blob_get(ssb, app_blob_id, &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 = JS_ParseJSON(context, (const char*)app_blob, app_blob_size, NULL);
JSValue files = JS_GetPropertyStr(context, app, "files");
if (!_has_property(context, files, "app.js"))
{
JSValue index = JS_GetPropertyStr(context, files, "index.html");
if (JS_IsString(index))
{
const char* index_string = JS_ToCString(context, index);
tf_ssb_db_blob_get(ssb, index_string, (uint8_t**)&data->data, &data->size);
JS_FreeCString(context, index_string);
}
JS_FreeValue(context, index);
}
JS_FreeValue(context, files);
JS_FreeValue(context, app);
JS_FreeContext(context);
JS_FreeRuntime(runtime);
tf_free(app_blob);
}
tf_free((void*)app_blob_id);
}
static char* _replace(const char* original, size_t size, const char* find, const char* replace, size_t* out_size)
{
char* pos = strstr(original, find);
if (!pos)
{
return tf_strdup(original);
}
size_t replace_length = strlen(replace);
size_t find_length = strlen(find);
size_t new_size = size + replace_length - find_length;
char* buffer = tf_malloc(new_size);
memcpy(buffer, original, pos - original);
memcpy(buffer + (pos - original), replace, replace_length);
memcpy(buffer + (pos - original) + replace_length, pos + find_length, size - (pos - original) - find_length);
*out_size = new_size;
return buffer;
}
static char* _append_raw(char* document, size_t* current_size, const char* data, size_t size)
{
document = tf_resize_vec(document, *current_size + size);
memcpy(document + *current_size, data, size);
document[*current_size + size] = '\0';
*current_size += size;
return document;
}
static char* _append_encoded(char* document, const char* data, size_t size, size_t* out_size)
{
size_t current_size = strlen(document);
int accum = 0;
for (int i = 0; (size_t)i < size; i++)
{
switch (data[i])
{
case '"':
if (i > accum)
{
document = _append_raw(document, &current_size, data + accum, i - accum);
}
document = _append_raw(document, &current_size, "&quot;", strlen("&quot;"));
accum = i + 1;
break;
case '\'':
if (i > accum)
{
document = _append_raw(document, &current_size, data + accum, i - accum);
}
document = _append_raw(document, &current_size, "&#x27;", strlen("&#x27;"));
accum = i + 1;
break;
case '<':
if (i > accum)
{
document = _append_raw(document, &current_size, data + accum, i - accum);
}
document = _append_raw(document, &current_size, "&lt;", strlen("&lt;"));
accum = i + 1;
break;
case '>':
if (i > accum)
{
document = _append_raw(document, &current_size, data + accum, i - accum);
}
document = _append_raw(document, &current_size, "&gt;", strlen("&gt;"));
accum = i + 1;
break;
case '&':
if (i > accum)
{
document = _append_raw(document, &current_size, data + accum, i - accum);
}
document = _append_raw(document, &current_size, "&amp;", strlen("&amp;"));
accum = i + 1;
break;
default:
break;
}
}
*out_size = current_size;
return document;
}
static void _httpd_endpoint_app_index_file_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{
app_blob_t* state = user_data;
if (result > 0)
{
char* replacement = tf_strdup("<iframe srcdoc=\"");
size_t replacement_size = 0;
replacement = _append_encoded(replacement, state->data, state->size, &replacement_size);
_append_raw(replacement, &replacement_size, "\"", 1);
size_t size = 0;
char* document = _replace(data, result, "<iframe", replacement, &size);
const char* headers[] = {
"Content-Type",
"text/html; charset=utf-8",
};
tf_http_respond(state->request, 200, headers, tf_countof(headers) / 2, document, size);
tf_free(replacement);
tf_free(document);
}
tf_free(state->data);
tf_free(state->user_app);
tf_http_request_unref(state->request);
tf_free(state);
}
static void _httpd_endpoint_app_index_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
app_blob_t* data = user_data;
if (data->data)
{
tf_task_t* task = data->request->user_data;
const char* root_path = tf_task_get_root_path(task);
size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen("core/index.html") + 1;
char* path = alloca(size);
snprintf(path, size, "%s%score/index.html", root_path ? root_path : "", root_path ? "/" : "");
tf_file_read(task, path, _httpd_endpoint_app_index_file_read, data);
}
else
{
_httpd_endpoint_static(data->request);
tf_free(data->user_app);
tf_http_request_unref(data->request);
tf_free(data);
}
}
static void _httpd_endpoint_app_index(tf_http_request_t* request)
{
user_app_t* user_app = _parse_user_app_from_path(request->path, "/");
if (!user_app)
{
return _httpd_endpoint_static(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, .user_app = user_app };
tf_http_request_ref(request);
tf_ssb_run_work(ssb, _httpd_endpoint_app_index_work, _httpd_endpoint_app_index_after_work, data);
}
typedef struct _view_t
{
tf_http_request_t* request;
@@ -2363,7 +2566,7 @@ tf_http_t* tf_httpd_create(JSContext* context)
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}/", _httpd_endpoint_app_index, 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);