forked from cory/tildefriends
		
	Serve core static files without leaving C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4833 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		@@ -11,7 +11,7 @@
 | 
			
		||||
		<tf-auth id="auth"></tf-auth>
 | 
			
		||||
		<script>window.litDisableBundleWarning = true;</script>
 | 
			
		||||
		<script type="module">
 | 
			
		||||
			import {LitElement, html} from '/static/lit/lit-all.min.js';
 | 
			
		||||
			import {LitElement, html} from '/lit/lit-all.min.js';
 | 
			
		||||
			let g_data = $AUTH_DATA;
 | 
			
		||||
			let app = document.getElementById('auth');
 | 
			
		||||
			Object.assign(app, g_data);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {LitElement, html, css, svg} from '/static/lit/lit-all.min.js';
 | 
			
		||||
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
 | 
			
		||||
 | 
			
		||||
let cm6;
 | 
			
		||||
let gSocket;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								core/core.js
									
									
									
									
									
								
							@@ -35,12 +35,6 @@ const k_magic_bytes = [
 | 
			
		||||
 | 
			
		||||
let k_static_files = [
 | 
			
		||||
	{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
 | 
			
		||||
	{uri: '/client.js', type: 'text/javascript; charset=UTF-8'},
 | 
			
		||||
	{uri: '/favicon.png', type: 'image/png'},
 | 
			
		||||
	{uri: '/jszip.min.js', type: 'text/javascript; charset=UTF-8'},
 | 
			
		||||
	{uri: '/style.css', type: 'text/css; charset=UTF-8'},
 | 
			
		||||
	{uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}},
 | 
			
		||||
	{uri: '/w3.css', type: 'text/css; charset=UTF-8'},
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const k_global_settings = {
 | 
			
		||||
@@ -562,37 +556,6 @@ function startsWithBytes(data, bytes) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function staticFileHandler(request, response, blobId, uri) {
 | 
			
		||||
	for (let i in k_static_files) {
 | 
			
		||||
		if (uri === k_static_files[i].uri) {
 | 
			
		||||
			let path = k_static_files[i].path || uri.substring(1);
 | 
			
		||||
			let type = k_static_files[i].type || guessTypeFromName(path);
 | 
			
		||||
 | 
			
		||||
			let stat = await File.stat('core/' + path);
 | 
			
		||||
			let id = `${stat.mtime}_${stat.size}`;
 | 
			
		||||
 | 
			
		||||
			if (request.headers['if-none-match'] === '"' + id + '"') {
 | 
			
		||||
				response.writeHead(304, {'Content-Length': '0'});
 | 
			
		||||
				response.end();
 | 
			
		||||
			} else {
 | 
			
		||||
				let data = await File.readFile('core/' + path);
 | 
			
		||||
				response.writeHead(200, Object.assign(
 | 
			
		||||
					{
 | 
			
		||||
						'Content-Type': type,
 | 
			
		||||
						'Content-Length': data.byteLength,
 | 
			
		||||
						'etag': '"' + id + '"',
 | 
			
		||||
					},
 | 
			
		||||
					k_static_files[i].headers || {}));
 | 
			
		||||
				response.end(data);
 | 
			
		||||
			}
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
 | 
			
		||||
	response.end("File not found");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function staticDirectoryHandler(request, response, directory, uri) {
 | 
			
		||||
	let filename = uri || 'index.html';
 | 
			
		||||
	if (filename.indexOf('..') != -1) {
 | 
			
		||||
@@ -983,6 +946,7 @@ loadSettings().then(function() {
 | 
			
		||||
		httpd.set_http_redirect(gGlobalSettings.http_redirect);
 | 
			
		||||
	}
 | 
			
		||||
	httpd.all("/login", auth.handler);
 | 
			
		||||
	httpd.all("/login/logout", auth.handler);
 | 
			
		||||
	httpd.all("/app/socket", app.socket);
 | 
			
		||||
	httpd.all("", function default_http_handler(request, response) {
 | 
			
		||||
		let match;
 | 
			
		||||
@@ -1004,14 +968,12 @@ loadSettings().then(function() {
 | 
			
		||||
			return blobHandler(request, response, match[1], match[2]);
 | 
			
		||||
		} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
 | 
			
		||||
			return blobHandler(request, response, match[1], match[2]);
 | 
			
		||||
		} else if (match = /^\/static\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
 | 
			
		||||
		} else if (match = /^\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
 | 
			
		||||
			return staticDirectoryHandler(request, response, 'deps/lit/', match[1]);
 | 
			
		||||
		} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
 | 
			
		||||
			return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
 | 
			
		||||
		} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
 | 
			
		||||
			return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
 | 
			
		||||
		} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
 | 
			
		||||
			return staticFileHandler(request, response, null, match[1]);
 | 
			
		||||
		} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
 | 
			
		||||
			return blobHandler(request, response, match[1], match[2]);
 | 
			
		||||
		} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										160
									
								
								src/file.js.c
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								src/file.js.c
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
#include "file.js.h"
 | 
			
		||||
 | 
			
		||||
#include "log.h"
 | 
			
		||||
#include "mem.h"
 | 
			
		||||
#include "task.h"
 | 
			
		||||
#include "trace.h"
 | 
			
		||||
@@ -51,7 +52,7 @@ void tf_file_register(JSContext* context)
 | 
			
		||||
	JS_FreeValue(context, global);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const int k_file_read_max = 8 * 1024 * 1024;
 | 
			
		||||
enum { k_file_read_max = 8 * 1024 * 1024 };
 | 
			
		||||
 | 
			
		||||
static void _file_async_close_callback(uv_fs_t* req)
 | 
			
		||||
{
 | 
			
		||||
@@ -426,3 +427,160 @@ static void _file_on_stat_complete(uv_fs_t* request)
 | 
			
		||||
	uv_fs_req_cleanup(request);
 | 
			
		||||
	tf_free(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef struct _stat_t
 | 
			
		||||
{
 | 
			
		||||
	uv_fs_t request;
 | 
			
		||||
	tf_task_t* task;
 | 
			
		||||
	void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data);
 | 
			
		||||
	void* user_data;
 | 
			
		||||
	char path[];
 | 
			
		||||
} stat_t;
 | 
			
		||||
 | 
			
		||||
static void _file_stat_complete(uv_fs_t* request)
 | 
			
		||||
{
 | 
			
		||||
	stat_t* data = request->data;
 | 
			
		||||
	data->callback(data->task, data->path, request->result, &request->statbuf, data->user_data);
 | 
			
		||||
	uv_fs_req_cleanup(request);
 | 
			
		||||
	tf_free(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data)
 | 
			
		||||
{
 | 
			
		||||
	const char* zip = tf_task_get_zip_path(task);
 | 
			
		||||
	size_t path_length = strlen(path) + 1;
 | 
			
		||||
	stat_t* data = tf_malloc(sizeof(stat_t) + path_length);
 | 
			
		||||
	*data = (stat_t) { .request = { .data = data }, .task = task, .callback = callback, .user_data = user_data };
 | 
			
		||||
	memcpy(data->path, path, path_length);
 | 
			
		||||
 | 
			
		||||
	int result = uv_fs_stat(tf_task_get_loop(task), &data->request, zip ? zip : path, _file_stat_complete);
 | 
			
		||||
	if (result)
 | 
			
		||||
	{
 | 
			
		||||
		callback(task, path, result, NULL, user_data);
 | 
			
		||||
		uv_fs_req_cleanup(&data->request);
 | 
			
		||||
		tf_free(data);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef struct _read_t
 | 
			
		||||
{
 | 
			
		||||
	uv_work_t work;
 | 
			
		||||
	tf_task_t* task;
 | 
			
		||||
	void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data);
 | 
			
		||||
	void* user_data;
 | 
			
		||||
	int64_t result;
 | 
			
		||||
	char buffer[k_file_read_max];
 | 
			
		||||
	char path[];
 | 
			
		||||
} read_t;
 | 
			
		||||
 | 
			
		||||
static void _file_read_work(uv_work_t* work)
 | 
			
		||||
{
 | 
			
		||||
	read_t* data = work->data;
 | 
			
		||||
	const char* zip_path = tf_task_get_zip_path(data->task);
 | 
			
		||||
	if (zip_path)
 | 
			
		||||
	{
 | 
			
		||||
		tf_trace_t* trace = tf_task_get_trace(data->task);
 | 
			
		||||
		tf_trace_begin(trace, "file_read_zip_work");
 | 
			
		||||
		unzFile zip = unzOpen(zip_path);
 | 
			
		||||
		if (zip)
 | 
			
		||||
		{
 | 
			
		||||
			data->result = unzLocateFile(zip, data->path, 1);
 | 
			
		||||
			if (data->result == UNZ_OK)
 | 
			
		||||
			{
 | 
			
		||||
				unz_file_info64 info = { 0 };
 | 
			
		||||
				data->result = unzGetCurrentFileInfo64(zip, &info, NULL, 0, NULL, 0, NULL, 0);
 | 
			
		||||
				if (data->result == UNZ_OK && info.uncompressed_size > sizeof(data->buffer))
 | 
			
		||||
				{
 | 
			
		||||
					data->result = -EFBIG;
 | 
			
		||||
				}
 | 
			
		||||
				else if (data->result == UNZ_OK)
 | 
			
		||||
				{
 | 
			
		||||
					data->result = unzOpenCurrentFile(zip);
 | 
			
		||||
					if (data->result == UNZ_OK)
 | 
			
		||||
					{
 | 
			
		||||
						data->result = unzReadCurrentFile(zip, data->buffer, info.uncompressed_size);
 | 
			
		||||
						if (data->result == (int64_t)info.uncompressed_size)
 | 
			
		||||
						{
 | 
			
		||||
							int r = unzCloseCurrentFile(zip);
 | 
			
		||||
							if (r != UNZ_OK)
 | 
			
		||||
							{
 | 
			
		||||
								data->result = r;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						else
 | 
			
		||||
						{
 | 
			
		||||
							data->result = EAGAIN;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			unzClose(zip);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			data->result = errno;
 | 
			
		||||
		}
 | 
			
		||||
		tf_trace_end(trace);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_trace_t* trace = tf_task_get_trace(data->task);
 | 
			
		||||
		tf_trace_begin(trace, "file_read_zip_work");
 | 
			
		||||
		uv_loop_t* loop = tf_task_get_loop(data->task);
 | 
			
		||||
		uv_fs_t open_req = { 0 };
 | 
			
		||||
		int open_result = uv_fs_open(loop, &open_req, data->path, UV_FS_O_RDONLY, 0, NULL);
 | 
			
		||||
		if (open_result >= 0)
 | 
			
		||||
		{
 | 
			
		||||
			uv_buf_t buf = { .base = data->buffer, .len = sizeof(data->buffer) };
 | 
			
		||||
			uv_fs_t read_req = { 0 };
 | 
			
		||||
			int result = uv_fs_read(loop, &read_req, open_result, &buf, 1, 0, NULL);
 | 
			
		||||
			if ((size_t)result >= sizeof(data->buffer))
 | 
			
		||||
			{
 | 
			
		||||
				data->result = -EFBIG;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				data->result = result;
 | 
			
		||||
			}
 | 
			
		||||
			uv_fs_req_cleanup(&read_req);
 | 
			
		||||
 | 
			
		||||
			uv_fs_t close_req = { 0 };
 | 
			
		||||
			result = uv_fs_close(loop, &close_req, open_result, NULL);
 | 
			
		||||
			if (result && data->result >= 0)
 | 
			
		||||
			{
 | 
			
		||||
				data->result = result;
 | 
			
		||||
			}
 | 
			
		||||
			uv_fs_req_cleanup(&close_req);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			data->result = errno;
 | 
			
		||||
		}
 | 
			
		||||
		uv_fs_req_cleanup(&open_req);
 | 
			
		||||
		tf_trace_end(trace);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _file_read_after_work(uv_work_t* work, int result)
 | 
			
		||||
{
 | 
			
		||||
	read_t* data = work->data;
 | 
			
		||||
	data->callback(data->task, data->path, data->result, data->buffer, data->user_data);
 | 
			
		||||
	tf_free(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data)
 | 
			
		||||
{
 | 
			
		||||
	size_t path_length = strlen(path) + 1;
 | 
			
		||||
	read_t* data = tf_malloc(sizeof(read_t) + path_length);
 | 
			
		||||
	memset(data, 0, sizeof(read_t));
 | 
			
		||||
	data->callback = callback;
 | 
			
		||||
	data->user_data = user_data;
 | 
			
		||||
	data->work.data = data;
 | 
			
		||||
	data->task = task;
 | 
			
		||||
	memcpy(data->path, path, path_length);
 | 
			
		||||
	int r = uv_queue_work(tf_task_get_loop(task), &data->work, _file_read_work, _file_read_after_work);
 | 
			
		||||
	if (r)
 | 
			
		||||
	{
 | 
			
		||||
		_file_read_after_work(&data->work, r);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <uv.h>
 | 
			
		||||
 | 
			
		||||
typedef struct JSContext JSContext;
 | 
			
		||||
typedef struct _tf_task_t tf_task_t;
 | 
			
		||||
 | 
			
		||||
void tf_file_register(JSContext* context);
 | 
			
		||||
 | 
			
		||||
void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data);
 | 
			
		||||
void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/http.c
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/http.c
									
									
									
									
									
								
							@@ -102,7 +102,6 @@ typedef struct _tf_http_t
 | 
			
		||||
 | 
			
		||||
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name);
 | 
			
		||||
static void _http_connection_destroy(tf_http_connection_t* connection, const char* reason);
 | 
			
		||||
static const char* _http_status_text(int status);
 | 
			
		||||
static void _http_timer_reset(tf_http_connection_t* connection);
 | 
			
		||||
static void _http_tls_update(tf_http_connection_t* connection);
 | 
			
		||||
 | 
			
		||||
@@ -132,8 +131,9 @@ bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t**
 | 
			
		||||
	for (int i = 0; i < http->handlers_count; i++)
 | 
			
		||||
	{
 | 
			
		||||
		if (!http->handlers[i].pattern ||
 | 
			
		||||
			!*http->handlers[i].pattern ||
 | 
			
		||||
			strcmp(path, http->handlers[i].pattern) == 0 ||
 | 
			
		||||
			(strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/'))
 | 
			
		||||
			(*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/'))
 | 
			
		||||
		{
 | 
			
		||||
			*out_callback = http->handlers[i].callback;
 | 
			
		||||
			*out_trace_name = http->handlers[i].pattern;
 | 
			
		||||
@@ -213,7 +213,7 @@ static void _http_connection_destroy(tf_http_connection_t* connection, const cha
 | 
			
		||||
 | 
			
		||||
static void _http_builtin_404_handler(tf_http_request_t* request)
 | 
			
		||||
{
 | 
			
		||||
	const char* k_payload = _http_status_text(404);
 | 
			
		||||
	const char* k_payload = tf_http_status_text(404);
 | 
			
		||||
	tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -731,7 +731,7 @@ void tf_http_destroy(tf_http_t* http)
 | 
			
		||||
	tf_free(http);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char* _http_status_text(int status)
 | 
			
		||||
const char* tf_http_status_text(int status)
 | 
			
		||||
{
 | 
			
		||||
	switch (status)
 | 
			
		||||
	{
 | 
			
		||||
@@ -848,7 +848,7 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header
 | 
			
		||||
	}
 | 
			
		||||
	request->connection->is_response_sent = true;
 | 
			
		||||
 | 
			
		||||
	const char* status_text = _http_status_text(status);
 | 
			
		||||
	const char* status_text = tf_http_status_text(status);
 | 
			
		||||
	/* HTTP/1.x 200 OK\r\n */
 | 
			
		||||
	bool sent_content_length = false;
 | 
			
		||||
	int headers_length = 8 + 1 + 3 + 1 + strlen(status_text) + 2;
 | 
			
		||||
 
 | 
			
		||||
@@ -51,3 +51,5 @@ void tf_http_request_unref(tf_http_request_t* request);
 | 
			
		||||
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name);
 | 
			
		||||
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
 | 
			
		||||
void tf_http_request_websocket_upgrade(tf_http_request_t* request);
 | 
			
		||||
 | 
			
		||||
const char* tf_http_status_text(int status);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								src/httpd.js.c
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								src/httpd.js.c
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
#include "httpd.js.h"
 | 
			
		||||
 | 
			
		||||
#include "file.js.h"
 | 
			
		||||
#include "http.h"
 | 
			
		||||
#include "log.h"
 | 
			
		||||
#include "mem.h"
 | 
			
		||||
@@ -10,6 +11,7 @@
 | 
			
		||||
 | 
			
		||||
#include "picohttpparser.h"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
@@ -508,6 +510,150 @@ static void _httpd_endpoint_hitches(tf_http_request_t* request)
 | 
			
		||||
	tf_free(response);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char* _after(const char* text, const char* prefix)
 | 
			
		||||
{
 | 
			
		||||
	size_t prefix_length = strlen(prefix);
 | 
			
		||||
	if (strncmp(text, prefix, prefix_length) == 0)
 | 
			
		||||
	{
 | 
			
		||||
		return text + prefix_length;
 | 
			
		||||
	}
 | 
			
		||||
	return text;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static double _time_spec_to_double(const uv_timespec_t* time_spec)
 | 
			
		||||
{
 | 
			
		||||
	return (double)time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef struct _http_file_t
 | 
			
		||||
{
 | 
			
		||||
	tf_http_request_t* request;
 | 
			
		||||
	char etag[512];
 | 
			
		||||
} http_file_t;
 | 
			
		||||
 | 
			
		||||
static const char* _ext_to_content_type(const char* ext)
 | 
			
		||||
{
 | 
			
		||||
	if (ext)
 | 
			
		||||
	{
 | 
			
		||||
		if (strcmp(ext, ".js") == 0)
 | 
			
		||||
		{
 | 
			
		||||
			return "text/javascript; charset=UTF-8";
 | 
			
		||||
		}
 | 
			
		||||
		if (strcmp(ext, ".css") == 0)
 | 
			
		||||
		{
 | 
			
		||||
			return "text/css; charset=UTF-8";
 | 
			
		||||
		}
 | 
			
		||||
		else if (strcmp(ext, ".png") == 0)
 | 
			
		||||
		{
 | 
			
		||||
			return "image/png";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "application/binary";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
 | 
			
		||||
{
 | 
			
		||||
	http_file_t* file = user_data;
 | 
			
		||||
	tf_http_request_t* request = file->request;
 | 
			
		||||
	if (result >= 0)
 | 
			
		||||
	{
 | 
			
		||||
		if (strcmp(path, "core/tfrpc.js") == 0)
 | 
			
		||||
		{
 | 
			
		||||
			const char* content_type = _ext_to_content_type(strrchr(path, '.'));
 | 
			
		||||
			const char* headers[] =
 | 
			
		||||
			{
 | 
			
		||||
				"Content-Type", content_type,
 | 
			
		||||
				"etag", file->etag,
 | 
			
		||||
				"Access-Control-Allow-Origin", "null",
 | 
			
		||||
			};
 | 
			
		||||
			tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			const char* content_type = _ext_to_content_type(strrchr(path, '.'));
 | 
			
		||||
			const char* headers[] =
 | 
			
		||||
			{
 | 
			
		||||
				"Content-Type", content_type,
 | 
			
		||||
				"etag", file->etag,
 | 
			
		||||
			};
 | 
			
		||||
			tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_printf("404 %s\n", path);
 | 
			
		||||
		const char* k_payload = tf_http_status_text(404);
 | 
			
		||||
		tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
 | 
			
		||||
	}
 | 
			
		||||
	tf_http_request_unref(request);
 | 
			
		||||
	tf_free(file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _httpd_endpoint_static_stat(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data)
 | 
			
		||||
{
 | 
			
		||||
	tf_http_request_t* request = user_data;
 | 
			
		||||
	const char* match = tf_http_request_get_header(request, "if-none-match");
 | 
			
		||||
	char etag[512];
 | 
			
		||||
	snprintf(etag, sizeof(etag), "\"%f_%zd\"", _time_spec_to_double(&stat->st_mtim), (size_t)stat->st_size);
 | 
			
		||||
	if (match && strcmp(match, etag) == 0)
 | 
			
		||||
	{
 | 
			
		||||
		tf_http_respond(request, 304, NULL, 0, NULL, 0);
 | 
			
		||||
		tf_http_request_unref(request);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		http_file_t* file = tf_malloc(sizeof(http_file_t));
 | 
			
		||||
		*file = (http_file_t) { .request = request };
 | 
			
		||||
		static_assert(sizeof(file->etag) == sizeof(etag), "Size mismatch");
 | 
			
		||||
		memcpy(file->etag, etag, sizeof(etag));
 | 
			
		||||
		tf_file_read(task, path, _httpd_endpoint_static_read, file);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _httpd_endpoint_static(tf_http_request_t* request)
 | 
			
		||||
{
 | 
			
		||||
	if (_httpd_redirect(request))
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const char* k_static_files[] =
 | 
			
		||||
	{
 | 
			
		||||
		"index.html",
 | 
			
		||||
		"client.js",
 | 
			
		||||
		"favicon.png",
 | 
			
		||||
		"jszip.min.js",
 | 
			
		||||
		"style.css",
 | 
			
		||||
		"tfrpc.js",
 | 
			
		||||
		"w3.css",
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	tf_task_t* task = request->user_data;
 | 
			
		||||
	const char* after = _after(request->path, "/static/");
 | 
			
		||||
	bool found = false;
 | 
			
		||||
	for (int i = 0; i < tf_countof(k_static_files); i++)
 | 
			
		||||
	{
 | 
			
		||||
		if (strcmp(after, k_static_files[i]) == 0)
 | 
			
		||||
		{
 | 
			
		||||
			found = true;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!found)
 | 
			
		||||
	{
 | 
			
		||||
		const char* k_payload = tf_http_status_text(404);
 | 
			
		||||
		tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t size = strlen("core/") + strlen(after) + 1;
 | 
			
		||||
	char* path = alloca(size);
 | 
			
		||||
	snprintf(path, size, "core/%s", after);
 | 
			
		||||
	tf_http_request_ref(request);
 | 
			
		||||
	tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void _httpd_endpoint_robots_txt(tf_http_request_t* request)
 | 
			
		||||
{
 | 
			
		||||
	if (_httpd_redirect(request))
 | 
			
		||||
@@ -571,6 +717,7 @@ void tf_httpd_register(JSContext* context)
 | 
			
		||||
	tf_http_set_trace(http, tf_task_get_trace(task));
 | 
			
		||||
	JS_SetOpaque(httpd, http);
 | 
			
		||||
 | 
			
		||||
	tf_http_add_handler(http, "/static/", _httpd_endpoint_static, 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);
 | 
			
		||||
	tf_http_add_handler(http, "/disconnections", _httpd_endpoint_disconnections, NULL, task);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user