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 += '\n';
} else {
- contents += 'You are already logged in, ' + entry.name + '.
\n';
+ contents += '';
}
- contents += '\n';
- } else {
- 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");
}