#include "httpd.js.h" #include "file.js.h" #include "http.h" #include "mem.h" #include "task.h" #include "util.js.h" #include #include #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) #include #endif typedef struct _http_file_t { tf_http_request_t* request; char etag[512]; } http_file_t; static bool _ends_with(const char* a, const char* suffix) { if (!a || !suffix) { return false; } size_t alen = strlen(a); size_t suffixlen = strlen(suffix); return alen >= suffixlen && strcmp(a + alen - suffixlen, suffix) == 0; } static const char* _after(const char* text, const char* prefix) { size_t prefix_length = strlen(prefix); if (text && strncmp(text, prefix, prefix_length) == 0) { return text + prefix_length; } return NULL; } static double _time_spec_to_double(const uv_timespec_t* time_spec) { return (double)time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9; } 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 || _ends_with(path, "core/tfrpc.js")) { const char* content_type = tf_httpd_ext_to_content_type(strrchr(path, '.'), true); 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 = tf_httpd_ext_to_content_type(strrchr(path, '.'), true); const char* headers[] = { "Content-Type", content_type, "etag", file->etag, }; tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result); } } else { 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"); if (result != 0) { 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); } else { 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); } } } void tf_httpd_endpoint_static(tf_http_request_t* request) { if (request->path && strncmp(request->path, "/.well-known/", strlen("/.well-known/")) && tf_httpd_redirect(request)) { return; } const char* k_static_files[] = { "index.html", "client.js", "tildefriends.svg", "jszip.min.js", "style.css", "tfrpc.js", "w3.css", }; const char* k_map[][2] = { { "/static/", "core/" }, { "/lit/", "deps/lit/" }, { "/codemirror/", "deps/codemirror/" }, { "/prettier/", "deps/prettier/" }, { "/speedscope/", "deps/speedscope/" }, { "/.well-known/", "data/global/.well-known/" }, }; bool is_core = false; const char* after = NULL; const char* file_path = NULL; for (int i = 0; i < tf_countof(k_map) && !after; i++) { const char* next_after = _after(request->path, k_map[i][0]); if (next_after) { after = next_after; file_path = k_map[i][1]; is_core = after && i == 0; } } if ((!after || !*after) && request->path[strlen(request->path) - 1] == '/') { after = "index.html"; if (!file_path) { file_path = "core/"; is_core = true; } } if (!after || strstr(after, "..")) { const char* k_payload = tf_http_status_text(404); tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload)); return; } if (is_core) { 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; } } tf_task_t* task = request->user_data; const char* root_path = tf_task_get_root_path(task); size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen(file_path) + strlen(after) + 1; char* path = alloca(size); snprintf(path, size, "%s%s%s%s", root_path ? root_path : "", root_path ? "/" : "", file_path, after); tf_http_request_ref(request); tf_file_stat(task, path, _httpd_endpoint_static_stat, request); }