diff --git a/core/auth.js b/core/auth.js index 8632a1f3..7e66a8f4 100644 --- a/core/auth.js +++ b/core/auth.js @@ -120,43 +120,48 @@ function authHandler(request, response) { response.writeHead(303, {"Location": formData.return, "Set-Cookie": cookie}); response.end(); } else { - var html = new TextDecoder("UTF-8").decode(File.readFile("core/auth.html")); - var contents = ""; + File.readFile("core/auth.html").then(function(data) { + var html = new TextDecoder("UTF-8").decode(data); + var contents = ""; - if (entry) { - if (sessionIsNew) { - contents += '
Welcome back, ' + entry.name + '.
\n'; + if (entry) { + if (sessionIsNew) { + contents += '
Welcome back, ' + entry.name + '.
\n'; + } else { + contents += '
You are already logged in, ' + entry.name + '.
\n'; + } + contents += '
Logout
\n'; } else { - contents += '
You are already logged in, ' + entry.name + '.
\n'; + contents += '
\n'; + if (loginError) { + contents += "

" + loginError + "

\n"; + } + contents += '
Halt. Who goes there?
\n' + contents += '
\n'; + contents += '
\n' + if (noAdministrator()) { + contents += '
There is currently no administrator. You will be made administrator.
\n'; + } + contents += '
\n'; + contents += '
\n'; + contents += '\n'; + contents += '
\n'; + contents += '
\n'; + contents += '
'; + contents += '
- or -
'; + contents += '
\n'; + contents += '\n'; + contents += '
\n'; + contents += '
\n'; + contents += '
'; } - contents += '
Logout
\n'; - } else { - contents += '
\n'; - if (loginError) { - contents += "

" + loginError + "

\n"; - } - contents += '
Halt. Who goes there?
\n' - contents += '
\n'; - contents += '
\n' - if (noAdministrator()) { - contents += '
There is currently no administrator. You will be made administrator.
\n'; - } - contents += '
\n'; - contents += '
\n'; - contents += '\n'; - contents += '
\n'; - contents += '
\n'; - contents += '
'; - contents += '
- or -
'; - contents += '
\n'; - contents += '\n'; - contents += '
\n'; - contents += '
\n'; - contents += '
'; - } - var text = html.replace("", contents); - response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": text.length}); - response.end(text); + var text = html.replace("", contents); + response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": text.length}); + response.end(text); + }).catch(function(error) { + response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"}); + response.end("404 File not found"); + }); } } else if (request.uri == "/login/logout") { removeSession(session); diff --git a/core/core.js b/core/core.js index 4ff0599d..de9ef811 100644 --- a/core/core.js +++ b/core/core.js @@ -138,48 +138,14 @@ async function getSessionProcessBlob(blobId, session, options) { return getProcessBlob(blobId, 'session_' + session, actualOptions); } -function readFileUtf8(fileName) { - let data = File.readFile(fileName); +async function readFileUtf8(fileName) { + let data = await File.readFile(fileName); data = utf8Decode(data); return data; } let gManifestCache = {}; -async function getManifest(fileName) { - let oldEntry = gManifestCache[fileName]; - let stat = await File.stat(fileName); - if (oldEntry) { - if (oldEntry.stat.mtime == stat.mtime && oldEntry.stat.size == stat.size) { - return oldEntry.manifest; - } - } - - let manifest = []; - let lines = readFileUtf8(fileName).split("\n").map(x => x.trimRight()); - for (let i = 0; i < lines.length; i++) { - if (lines[i].substring(0, 4) == "//! ") { - manifest.push(lines[i].substring(4)); - } - } - let result; - try { - if (manifest.length) { - result = JSON.parse(manifest.join("\n")); - } - } catch (error) { - print("ERROR: getManifest(" + fileName + "): ", error); - // Oh well. No manifest. - } - - gManifestCache[fileName] = { - stat: stat, - manifest: result, - }; - - return result; -} - async function getProcessBlob(blobId, key, options) { var process = gProcesses[key]; if (!process @@ -319,15 +285,6 @@ function setGlobalSettings(settings) { } } -try { - var data = readFileUtf8(kGlobalSettingsFile); - if (data) { - gGlobalSettings = JSON.parse(data); - } -} catch (error) { - print("Error loading settings from " + kGlobalSettingsFile + ": " + error); -} - var kStaticFiles = [ {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, {uri: '/style.css', path: 'style.css', type: 'text/css; charset=UTF-8'}, @@ -351,7 +308,7 @@ function startsWithBytes(data, bytes) { async function staticFileHandler(request, response, blobId, uri) { for (var i in kStaticFiles) { if (uri === kStaticFiles[i].uri) { - var data = File.readFile("core/" + kStaticFiles[i].path); + var data = await File.readFile("core/" + kStaticFiles[i].path); response.writeHead(200, {"Content-Type": kStaticFiles[i].type, "Content-Length": data.byteLength}); response.end(data); return; @@ -403,7 +360,7 @@ async function blobHandler(request, response, blobId, uri) { for (var i in kStaticFiles) { if (uri === kStaticFiles[i].uri) { found = true; - var data = File.readFile("core/" + kStaticFiles[i].path); + var data = await File.readFile("core/" + kStaticFiles[i].path); response.writeHead(200, {"Content-Type": kStaticFiles[i].type, "Content-Length": data.byteLength}); response.end(data); break; @@ -483,41 +440,56 @@ ssb.onConnectionsChanged = function() { broadcastEvent('onConnectionsChanged', []); } -var auth = require("auth"); -var httpd = require("httpd"); -httpd.all("/login", auth.handler); -httpd.all("", function(request, response) { - var match; - if (request.uri === "/" || request.uri === "") { - response.writeHead(303, {"Location": 'http://' + request.headers.host + gGlobalSettings.index, "Content-Length": "0"}); - return response.end(); - } else if (match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri)) { - 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(\/.*)/.exec(request.uri)) { - return staticFileHandler(request, response, null, match[1]); - } else if (match = /^(.*)(\/save)$/.exec(request.uri)) { - return blobHandler(request, response, match[1], match[2]); - } else if (match = /^\/trace$/.exec(request.uri)) { - var data = trace(); - response.writeHead(404, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()}); - return response.end(data); - } else if (request.uri == "/robots.txt") { - return blobHandler(request, response, null, request.uri); - } else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) { - var data = File.readFile("data/global/.well-known/" + match[1]); +async function loadSettings() { + try { + var data = await readFileUtf8(kGlobalSettingsFile); if (data) { - response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": data.length}); - response.end(data); - } else { - response.writeHead(404, {"Content-Type": "text/plain", "Content-Length": "File not found".length}); - response.end("File not found"); + gGlobalSettings = JSON.parse(data); } - } else { - var data = "File not found."; - response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": data.length.toString()}); - return response.end(data); + } catch (error) { + print("Error loading settings from " + kGlobalSettingsFile + ": " + error); } +} + +loadSettings().then(function() { + var auth = require("auth"); + var httpd = require("httpd"); + httpd.all("/login", auth.handler); + httpd.all("", function(request, response) { + var match; + if (request.uri === "/" || request.uri === "") { + response.writeHead(303, {"Location": 'http://' + request.headers.host + gGlobalSettings.index, "Content-Length": "0"}); + return response.end(); + } else if (match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri)) { + 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(\/.*)/.exec(request.uri)) { + return staticFileHandler(request, response, null, match[1]); + } else if (match = /^(.*)(\/save)$/.exec(request.uri)) { + return blobHandler(request, response, match[1], match[2]); + } else if (match = /^\/trace$/.exec(request.uri)) { + var data = trace(); + response.writeHead(404, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()}); + return response.end(data); + } else if (request.uri == "/robots.txt") { + return blobHandler(request, response, null, request.uri); + } else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) { + var data = File.readFile("data/global/.well-known/" + match[1]); + if (data) { + response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": data.length}); + response.end(data); + } else { + response.writeHead(404, {"Content-Type": "text/plain", "Content-Length": "File not found".length}); + response.end("File not found"); + } + } else { + var data = "File not found."; + response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": data.length.toString()}); + return response.end(data); + } + }); + httpd.registerSocketHandler("/app/socket", app.socket); +}).catch(function(error) { + print('Failed to load settings.'); }); -httpd.registerSocketHandler("/app/socket", app.socket); diff --git a/src/file.c b/src/file.c index c0041724..50f603de 100644 --- a/src/file.c +++ b/src/file.c @@ -45,41 +45,84 @@ void tf_file_init(JSContext* context) { JS_FreeValue(context, global); } -static void _free_array_buffer_data(JSRuntime* runtime, void* opaque, void* ptr) { - free(ptr); +static const int k_file_read_max = 4 * 1024 * 1024; + +static void _file_read_close_callback(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + free(req); +} + +static void _file_read_read_callback(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + tf_task_t* task = req->loop->data; + JSContext* context = tf_task_get_context(task); + promiseid_t promise = (promiseid_t)(intptr_t)req->data; + if (req->result >= 0) + { + JSValue arrayBuffer = JS_NewArrayBufferCopy(context, (uint8_t*)(req + 1), req->result); + JSValue global = JS_GetGlobalObject(context); + JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); + JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer); + JS_FreeValue(context, constructor); + JS_FreeValue(context, global); + JS_FreeValue(context, arrayBuffer); + tf_task_resolve_promise(task, promise, typedArray); + JS_FreeValue(context, typedArray); + } + else + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(req->result))); + } + int result = uv_fs_close(req->loop, req, req->file, _file_read_close_callback); + if (result < 0) + { + free(req); + } +} + +static void _file_read_open_callback(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + tf_task_t* task = req->loop->data; + JSContext* context = tf_task_get_context(task); + promiseid_t promise = (promiseid_t)(intptr_t)req->data; + if (req->result >= 0) + { + uv_buf_t buf = { .base = (char*)(req + 1), .len = k_file_read_max }; + uv_file file = req->result; + int result = uv_fs_read(req->loop, req, file, &buf, 1, 0, _file_read_read_callback); + if (result < 0) + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(result))); + result = uv_fs_close(req->loop, req, file, _file_read_close_callback); + } + } + else + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(req->result))); + free(req); + } } static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { + void* task = JS_GetContextOpaque(context); const char* file_name = JS_ToCString(context, argv[0]); - FILE* file = fopen(file_name, "rb"); - JS_FreeCString(context, file_name); - if (!file) { - return JS_NULL; - } - long size = 0; - if (fseek(file, 0, SEEK_END) == 0) { - size = ftell(file); + promiseid_t promise = tf_task_allocate_promise(task); + uv_fs_t* req = malloc(sizeof(uv_fs_t) + k_file_read_max); + *req = (uv_fs_t) + { + .data = (void*)(intptr_t)promise, + }; + int result = uv_fs_open(tf_task_get_loop(task), req, file_name, UV_FS_O_RDONLY, 0, _file_read_open_callback); + if (result < 0) + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(result))); } - if (size >= 0 && - size < 4 * 1024 * 1024 && - fseek(file, 0, SEEK_SET) == 0) { - uint8_t* data = malloc(size); - if (data && - fread(data, 1, size, file) == (size_t)size) { - JSValue arrayBuffer = JS_NewArrayBuffer(context, data, size, _free_array_buffer_data, NULL, false); - JSValue global = JS_GetGlobalObject(context); - JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); - JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer); - JS_FreeValue(context, constructor); - JS_FreeValue(context, global); - JS_FreeValue(context, arrayBuffer); - return typedArray; - } else if (data) { - free(data); - } - } - return JS_NULL; + JS_FreeCString(context, file_name); + return tf_task_get_promise(task, promise); } static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { diff --git a/src/task.c b/src/task.c index 25536bcf..b5f04909 100644 --- a/src/task.c +++ b/src/task.c @@ -1142,6 +1142,7 @@ tf_task_t* tf_task_create() { tf_task_t* task = malloc(sizeof(tf_task_t)); *task = (tf_task_t) { 0 }; task->_loop = uv_loop_new(); + task->_loop->data = task; ++_count; task->_runtime = JS_NewRuntime(); task->_context = JS_NewContext(task->_runtime); diff --git a/src/tests.c b/src/tests.c index 7293d383..9f67cdc8 100644 --- a/src/tests.c +++ b/src/tests.c @@ -480,6 +480,36 @@ static void _test_socket(const tf_test_options_t* options) unlink("out/test.js"); } +static void _test_file(const tf_test_options_t* options) +{ + FILE* file = fopen("out/test.js", "w"); + fprintf(file, + "'use strict';\n" + "File.readFile('out/test.js').then(function(data) {\n" + " print('READ', utf8Decode(data));\n" + "}).catch(function(error) {\n" + " print('ERROR', error);\n" + " exit(1);\n" + "});\n" + "File.readFile('out/missing.txt').then(function(data) {\n" + " print('READ', utf8Decode(data));\n" + " exit(1);\n" + "}).catch(function(error) {\n" + " print('expected error', error);\n" + "});\n"); + fclose(file); + + char command[256]; + snprintf(command, sizeof(command), "%s run --ssb-port=0 -s out/test.js", options->exe_path); + printf("%s\n", command); + int result = system(command); + printf("returned %d\n", WEXITSTATUS(result)); + assert(WIFEXITED(result)); + assert(WEXITSTATUS(result) == 0); + + unlink("out/test.js"); +} + static void _tf_test_run(const tf_test_options_t* options, const char* name, void (*test)(const tf_test_options_t* options)) { bool specified = false; @@ -521,5 +551,6 @@ void tf_tests(const tf_test_options_t* options) _tf_test_run(options, "icu", _test_icu); _tf_test_run(options, "uint8array", _test_uint8array); _tf_test_run(options, "socket", _test_socket); + _tf_test_run(options, "file", _test_file); printf("Tests completed.\n"); }