Compare commits

..

4 Commits

Author SHA1 Message Date
68817feeec js: Kill getSessionProcessBlob.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 14m37s
2024-10-16 19:50:31 -04:00
97661e2ca2 http: Fix some headers. 2024-10-16 19:26:26 -04:00
72def5ae6d js: Move /view to C. 2024-10-16 19:16:45 -04:00
e638b155a1 js: Kill gGlobalSettings. Decouples things a bit. 2024-10-16 18:11:08 -04:00
4 changed files with 194 additions and 147 deletions

View File

@ -10,7 +10,7 @@ let gSessionIndex = 0;
* @returns * @returns
*/ */
function makeSessionId() { function makeSessionId() {
return (gSessionIndex++).toString(); return 'session_' + (gSessionIndex++).toString();
} }
/** /**
@ -172,7 +172,7 @@ async function socket(request, response, client) {
0x1 0x1
); );
} else { } else {
process = await core.getSessionProcessBlob( process = await core.getProcessBlob(
blobId, blobId,
sessionId, sessionId,
options options

View File

@ -87,10 +87,6 @@ const k_global_settings = {
}, },
}; };
let gGlobalSettings = {
index: '/~core/apps/',
};
let kPingInterval = 60 * 1000; let kPingInterval = 60 * 1000;
/** /**
@ -262,23 +258,6 @@ function postMessageInternal(from, to, message) {
} }
} }
/**
* TODOC
* @param {*} blobId
* @param {*} session
* @param {*} options
* @returns
*/
async function getSessionProcessBlob(blobId, session, options) {
let actualOptions = {timeout: kPingInterval};
if (options) {
for (let i in options) {
actualOptions[i] = options[i];
}
}
return getProcessBlob(blobId, 'session_' + session, actualOptions);
}
/** /**
* TODOC * TODOC
* @param {*} blobId * @param {*} blobId
@ -306,7 +285,7 @@ async function getProcessBlob(blobId, key, options) {
} }
process.lastActive = Date.now(); process.lastActive = Date.now();
process.lastPing = null; process.lastPing = null;
process.timeout = options.timeout; process.timeout = kPingInterval;
process.ready = new Promise(function (resolve, reject) { process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve; resolveReady = resolve;
rejectReady = reject; rejectReady = reject;
@ -345,59 +324,59 @@ async function getProcessBlob(blobId, key, options) {
return []; return [];
} }
}, },
permissionsGranted: function () { permissionsGranted: async function () {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if ( if (
user && user &&
options?.packageOwner && options?.packageOwner &&
options?.packageName && options?.packageName &&
gGlobalSettings.userPermissions && settings.userPermissions &&
gGlobalSettings.userPermissions[user] && settings.userPermissions[user] &&
gGlobalSettings.userPermissions[user][options.packageOwner] settings.userPermissions[user][options.packageOwner]
) { ) {
return gGlobalSettings.userPermissions[user][ return settings.userPermissions[user][
options.packageOwner options.packageOwner
][options.packageName]; ][options.packageName];
} }
}, },
allPermissionsGranted: function () { allPermissionsGranted: async function () {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if ( if (
user && user &&
options?.packageOwner && options?.packageOwner &&
options?.packageName && options?.packageName &&
gGlobalSettings.userPermissions && settings.userPermissions &&
gGlobalSettings.userPermissions[user] settings.userPermissions[user]
) { ) {
return gGlobalSettings.userPermissions[user]; return settings.userPermissions[user];
} }
}, },
permissionsForUser: function (user) { permissionsForUser: async function (user) {
return ( let settings = await loadSettings();
(gGlobalSettings?.permissions return settings?.permissions?.[user] ?? [];
? gGlobalSettings.permissions[user]
: []) ?? []
);
}, },
apps: (user) => getApps(user, process), apps: (user) => getApps(user, process),
getSockets: getSockets, getSockets: getSockets,
permissionTest: function (permission) { permissionTest: async function (permission) {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if (!user || !options?.packageOwner || !options?.packageName) { if (!user || !options?.packageOwner || !options?.packageName) {
return; return;
} else if ( } else if (
gGlobalSettings.userPermissions && settings.userPermissions &&
gGlobalSettings.userPermissions[user] && settings.userPermissions[user] &&
gGlobalSettings.userPermissions[user][options.packageOwner] && settings.userPermissions[user][options.packageOwner] &&
gGlobalSettings.userPermissions[user][options.packageOwner][ settings.userPermissions[user][options.packageOwner][
options.packageName options.packageName
] && ] &&
gGlobalSettings.userPermissions[user][options.packageOwner][ settings.userPermissions[user][options.packageOwner][
options.packageName options.packageName
][permission] !== undefined ][permission] !== undefined
) { ) {
if ( if (
gGlobalSettings.userPermissions[user][options.packageOwner][ settings.userPermissions[user][options.packageOwner][
options.packageName options.packageName
][permission] ][permission]
) { ) {
@ -509,23 +488,24 @@ async function getProcessBlob(blobId, key, options) {
} }
}; };
if (process.credentials?.permissions?.administration) { if (process.credentials?.permissions?.administration) {
imports.core.globalSettingsDescriptions = function () { imports.core.globalSettingsDescriptions = async function () {
let settings = Object.assign({}, k_global_settings); let settings = Object.assign({}, k_global_settings);
for (let [key, value] of Object.entries(gGlobalSettings)) { for (let [key, value] of Object.entries(await loadSettings())) {
if (settings[key]) { if (settings[key]) {
settings[key].value = value; settings[key].value = value;
} }
} }
return settings; return settings;
}; };
imports.core.globalSettingsGet = function (key) { imports.core.globalSettingsGet = async function (key) {
return gGlobalSettings[key]; let settings = await loadSettings();
return settings?.[key];
}; };
imports.core.globalSettingsSet = async function (key, value) { imports.core.globalSettingsSet = async function (key, value) {
print('Setting', key, value); print('Setting', key, value);
await loadSettings(); let settings = await loadSettings();
gGlobalSettings[key] = value; settings[key] = value;
setGlobalSettings(gGlobalSettings); await setGlobalSettings(settings);
print('Done.'); print('Done.');
}; };
imports.core.deleteUser = async function (user) { imports.core.deleteUser = async function (user) {
@ -707,8 +687,9 @@ async function getProcessBlob(blobId, key, options) {
imports.ssb.addEventListener = undefined; imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined; imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined; imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) { imports.fetch = async function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts); let settings = await loadSettings();
return http.fetch(url, options, settings?.fetch_hosts);
}; };
if ( if (
@ -759,13 +740,13 @@ async function getProcessBlob(blobId, key, options) {
); );
}; };
} }
process.sendPermissions = function sendPermissions() { process.sendPermissions = async function sendPermissions() {
process.app.send({ process.app.send({
action: 'permissions', action: 'permissions',
permissions: imports.core.permissionsGranted(), permissions: await imports.core.permissionsGranted(),
}); });
}; };
process.resetPermission = function resetPermission(permission) { process.resetPermission = async function resetPermission(permission) {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
storePermission( storePermission(
user, user,
@ -774,7 +755,7 @@ async function getProcessBlob(blobId, key, options) {
permission, permission,
undefined undefined
); );
process.sendPermissions(); return process.sendPermissions();
}; };
process.task.setImports(imports); process.task.setImports(imports);
process.task.activate(); process.task.activate();
@ -807,7 +788,7 @@ async function getProcessBlob(blobId, key, options) {
broadcastEvent('onSessionBegin', [getUser(process, process)]); broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) { if (process.app) {
process.app.send({action: 'ready', version: version()}); process.app.send({action: 'ready', version: version()});
process.sendPermissions(); await process.sendPermissions();
} }
await process.task.execute({name: appSourceName, source: appSource}); await process.task.execute({name: appSourceName, source: appSource});
resolveReady(process); resolveReady(process);
@ -837,7 +818,6 @@ async function getProcessBlob(blobId, key, options) {
* @returns * @returns
*/ */
async function setGlobalSettings(settings) { async function setGlobalSettings(settings) {
gGlobalSettings = settings;
try { try {
return await new Database('core').set('settings', JSON.stringify(settings)); return await new Database('core').set('settings', JSON.stringify(settings));
} catch (error) { } catch (error) {
@ -965,66 +945,7 @@ async function blobHandler(request, response, blobId, uri) {
} }
let process; let process;
if (uri == '/view') { if (uri == '/save') {
let data;
let match;
let query = form.decodeForm(request.query);
let headers = {
'Content-Security-Policy': k_content_security_policy,
};
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
}
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let id = await new Database(match[1]).get('path:' + match[2]);
if (id) {
if (request.headers['if-none-match'] === '"' + id + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
data = await ssb.blobGet(id);
if (match[3]) {
let appObject = JSON.parse(data);
data = appObject.files[match[3]];
}
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + id + '"'}, headers)
);
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + blobId + '"'}, headers)
);
}
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
data = await ssb.blobGet(blobId);
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + blobId + '"'}, headers)
);
}
}
} else if (uri == '/save') {
let match; let match;
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1]; let user = match[1];
@ -1229,7 +1150,7 @@ async function loadSettings() {
data[key] = value.default_value; data[key] = value.default_value;
} }
} }
gGlobalSettings = data; return data;
} }
/** /**
@ -1254,9 +1175,9 @@ function sendStats() {
* TODOC * TODOC
*/ */
loadSettings() loadSettings()
.then(function () { .then(function (settings) {
if (tildefriends.https_port && gGlobalSettings.http_redirect) { if (tildefriends.https_port && settings.http_redirect) {
httpd.set_http_redirect(gGlobalSettings.http_redirect); httpd.set_http_redirect(settings.http_redirect);
} }
httpd.all('/app/socket', app.socket); httpd.all('/app/socket', app.socket);
httpd.all('', function default_http_handler(request, response) { httpd.all('', function default_http_handler(request, response) {
@ -1332,35 +1253,36 @@ loadSettings()
* @param {*} permission * @param {*} permission
* @param {*} allow * @param {*} allow
*/ */
function storePermission(user, packageOwner, packageName, permission, allow) { async function storePermission(user, packageOwner, packageName, permission, allow) {
if (!gGlobalSettings.userPermissions) { let settings = await loadSettings();
gGlobalSettings.userPermissions = {}; if (!settings.userPermissions) {
settings.userPermissions = {};
} }
if (!gGlobalSettings.userPermissions[user]) { if (!settings.userPermissions[user]) {
gGlobalSettings.userPermissions[user] = {}; settings.userPermissions[user] = {};
} }
if (!gGlobalSettings.userPermissions[user][packageOwner]) { if (!settings.userPermissions[user][packageOwner]) {
gGlobalSettings.userPermissions[user][packageOwner] = {}; settings.userPermissions[user][packageOwner] = {};
} }
if (!gGlobalSettings.userPermissions[user][packageOwner][packageName]) { if (!settings.userPermissions[user][packageOwner][packageName]) {
gGlobalSettings.userPermissions[user][packageOwner][packageName] = {}; settings.userPermissions[user][packageOwner][packageName] = {};
} }
if ( if (
gGlobalSettings.userPermissions[user][packageOwner][packageName][ settings.userPermissions[user][packageOwner][packageName][
permission permission
] !== allow ] !== allow
) { ) {
if (allow === undefined) { if (allow === undefined) {
delete gGlobalSettings.userPermissions[user][packageOwner][packageName][ delete settings.userPermissions[user][packageOwner][packageName][
permission permission
]; ];
} else { } else {
gGlobalSettings.userPermissions[user][packageOwner][packageName][ settings.userPermissions[user][packageOwner][packageName][
permission permission
] = allow; ] = allow;
} }
setGlobalSettings(gGlobalSettings); return setGlobalSettings(settings);
} }
} }
export {invoke, getSessionProcessBlob}; export {invoke, getProcessBlob};

2
deps/c-ares vendored

@ -1 +1 @@
Subproject commit 0c1c60dc60114812eb5950e6b50fa69d923a20e6 Subproject commit a57ff692eeab8d21c853dc1ddaf0164f517074c3

View File

@ -41,6 +41,8 @@ static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char*
static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name); static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name);
static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie); static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie);
const char** _form_data_decode(const char* data, int length);
const char* _form_data_get(const char** form_data, const char* key);
static JSClassID _httpd_class_id; static JSClassID _httpd_class_id;
static JSClassID _httpd_request_class_id; static JSClassID _httpd_request_class_id;
@ -555,14 +557,11 @@ static bool _magic_bytes_match(const magic_bytes_t* magic, const uint8_t* actual
return true; return true;
} }
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static const char* _httpd_mime_type_from_magic_bytes_internal(const uint8_t* bytes, size_t size)
{ {
JSValue result = JS_UNDEFINED; const char* type = "application/binary";
size_t size = 0;
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
if (bytes) if (bytes)
{ {
const magic_bytes_t k_magic_bytes[] = { const magic_bytes_t k_magic_bytes[] = {
{ {
.type = "image/jpeg", .type = "image/jpeg",
@ -627,12 +626,19 @@ static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueCons
{ {
if (_magic_bytes_match(&k_magic_bytes[i], bytes, size)) if (_magic_bytes_match(&k_magic_bytes[i], bytes, size))
{ {
result = JS_NewString(context, k_magic_bytes[i].type); type = k_magic_bytes[i].type;
break; break;
} }
} }
} }
return result; return type;
}
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
size_t size = 0;
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
return JS_NewString(context, _httpd_mime_type_from_magic_bytes_internal(bytes, size));
} }
static const char* _ext_to_content_type(const char* ext, bool use_fallback) static const char* _ext_to_content_type(const char* ext, bool use_fallback)
@ -959,6 +965,124 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
tf_file_stat(task, path, _httpd_endpoint_static_stat, request); tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
} }
typedef struct _view_t
{
tf_http_request_t* request;
const char** form_data;
void* data;
size_t size;
bool not_modified;
} view_t;
static bool _is_filename_safe(const char* filename)
{
if (!filename)
{
return NULL;
}
for (const char* p = filename; *p; p++)
{
if ((*p <= 'a' && *p >= 'z') &&
(*p <= 'A' && *p >= 'Z') &&
(*p <= '0' && *p >= '9') &&
*p != '.' &&
*p != '-' &&
*p != '_')
{
return false;
}
}
return strlen(filename) < 256;
}
static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
{
view_t* view = user_data;
tf_http_request_t* request = view->request;
char blob_id[256] = "";
if (request->path[0] == '/' && request->path[1] == '~')
{
char user[256] = "";
char path[1024] = "";
const char* slash = strchr(request->path + 2, '/');
if (slash)
{
snprintf(user, sizeof(user), "%.*s", (int)(slash - (request->path + 2)), request->path + 2);
snprintf(path, sizeof(path), "path:%.*s", (int)(strlen(slash + 1) - strlen("/view")), slash + 1);
const char* value = tf_ssb_db_get_property(ssb, user, path);
snprintf(blob_id, sizeof(blob_id), "%s", value);
tf_free((void*)value);
}
}
else if (request->path[0] == '/' && request->path[1] == '&')
{
snprintf(blob_id, sizeof(blob_id), "%.*s", (int)(strlen(request->path) - strlen("/view") - 1), request->path + 1);
}
if (*blob_id)
{
const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
char match[258];
snprintf(match, sizeof(match), "\"%s\"", blob_id);
if (if_none_match && strcmp(if_none_match, match))
{
view->not_modified = true;
}
else
{
tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size);
}
}
}
static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
view_t* view = user_data;
const char* filename = _form_data_get(view->form_data, "filename");
if (!_is_filename_safe(filename))
{
filename = NULL;
}
char content_disposition[512] = "";
if (filename)
{
snprintf(content_disposition, sizeof(content_disposition), "attachment; filename=%s", filename);
}
const char* headers[] = {
"Content-Security-Policy", "sandbox allow-downloads allow-top-navigation-by-user-activation",
"Content-Type", view->data ? _httpd_mime_type_from_magic_bytes_internal(view->data, view->size) : "text/plain",
filename ? "Content-Disposition" : NULL, filename ? content_disposition : NULL,
};
int count = filename ? tf_countof(headers) / 2 : (tf_countof(headers) / 2 - 1);
if (view->not_modified)
{
tf_http_respond(view->request, 304, headers, count, NULL, 0);
}
else if (view->data)
{
tf_http_respond(view->request, 200, headers, count, view->data, view->size);
tf_free(view->data);
}
else
{
const char* k_payload = tf_http_status_text(404);
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
}
tf_free(view->form_data);
tf_http_request_unref(view->request);
tf_free(view);
}
static void _httpd_endpoint_view(tf_http_request_t* request)
{
tf_http_request_ref(request);
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
view_t* view = tf_malloc(sizeof(view_t));
*view = (view_t) { .request = request, .form_data = _form_data_decode(request->query, request->query ? strlen(request->query) : 0) };
tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view);
}
static void _httpd_endpoint_root_callback(const char* path, void* user_data) static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{ {
tf_http_request_t* request = user_data; tf_http_request_t* request = user_data;
@ -1690,6 +1814,7 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/~*/*/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/~*/*/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/*/view", _httpd_endpoint_view, NULL, task);
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); 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, "/debug", _httpd_endpoint_debug, NULL, task);