forked from cory/tildefriends
		
	
		
			
				
	
	
		
			234 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "httpd.js.h"
 | |
| 
 | |
| #include "file.js.h"
 | |
| #include "http.h"
 | |
| #include "mem.h"
 | |
| #include "ssb.db.h"
 | |
| #include "ssb.h"
 | |
| #include "task.h"
 | |
| #include "util.js.h"
 | |
| 
 | |
| #include <stdlib.h>
 | |
| 
 | |
| #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
 | |
| #include <alloca.h>
 | |
| #endif
 | |
| 
 | |
| typedef struct _index_t
 | |
| {
 | |
| 	tf_http_request_t* request;
 | |
| 	bool found;
 | |
| 	bool not_modified;
 | |
| 	bool use_handler;
 | |
| 	bool use_static;
 | |
| 	void* data;
 | |
| 	size_t size;
 | |
| 	char app_blob_id[k_blob_id_len];
 | |
| 	const char* file;
 | |
| 	tf_httpd_user_app_t* user_app;
 | |
| 	char etag[256];
 | |
| } index_t;
 | |
| 
 | |
| 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)
 | |
| {
 | |
| 	index_t* data = user_data;
 | |
| 	data->use_static = true;
 | |
| 	tf_httpd_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, ¤t_size, data + accum, i - accum);
 | |
| 			}
 | |
| 			document = _append_raw(document, ¤t_size, """, strlen("""));
 | |
| 			accum = i + 1;
 | |
| 			break;
 | |
| 		case '\'':
 | |
| 			if (i > accum)
 | |
| 			{
 | |
| 				document = _append_raw(document, ¤t_size, data + accum, i - accum);
 | |
| 			}
 | |
| 			document = _append_raw(document, ¤t_size, "'", strlen("'"));
 | |
| 			accum = i + 1;
 | |
| 			break;
 | |
| 		case '<':
 | |
| 			if (i > accum)
 | |
| 			{
 | |
| 				document = _append_raw(document, ¤t_size, data + accum, i - accum);
 | |
| 			}
 | |
| 			document = _append_raw(document, ¤t_size, "<", strlen("<"));
 | |
| 			accum = i + 1;
 | |
| 			break;
 | |
| 		case '>':
 | |
| 			if (i > accum)
 | |
| 			{
 | |
| 				document = _append_raw(document, ¤t_size, data + accum, i - accum);
 | |
| 			}
 | |
| 			document = _append_raw(document, ¤t_size, ">", strlen(">"));
 | |
| 			accum = i + 1;
 | |
| 			break;
 | |
| 		case '&':
 | |
| 			if (i > accum)
 | |
| 			{
 | |
| 				document = _append_raw(document, ¤t_size, data + accum, i - accum);
 | |
| 			}
 | |
| 			document = _append_raw(document, ¤t_size, "&", strlen("&"));
 | |
| 			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)
 | |
| {
 | |
| 	index_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);
 | |
| 		replacement = _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)
 | |
| {
 | |
| 	index_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
 | |
| 	{
 | |
| 		tf_httpd_endpoint_static(data->request);
 | |
| 		tf_free(data->user_app);
 | |
| 		tf_http_request_unref(data->request);
 | |
| 		tf_free(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void tf_httpd_endpoint_app_index(tf_http_request_t* request)
 | |
| {
 | |
| 	tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/");
 | |
| 	if (!user_app)
 | |
| 	{
 | |
| 		return tf_httpd_endpoint_static(request);
 | |
| 	}
 | |
| 
 | |
| 	tf_task_t* task = request->user_data;
 | |
| 	tf_ssb_t* ssb = tf_task_get_ssb(task);
 | |
| 	index_t* data = tf_malloc(sizeof(index_t));
 | |
| 	(*data) = (index_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);
 | |
| }
 |