Compare commits
4 Commits
32db18b0d6
...
68817feeec
Author | SHA1 | Date | |
---|---|---|---|
68817feeec | |||
97661e2ca2 | |||
72def5ae6d | |||
e638b155a1 |
@ -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
|
||||||
|
196
core/core.js
196
core/core.js
@ -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
2
deps/c-ares
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 0c1c60dc60114812eb5950e6b50fa69d923a20e6
|
Subproject commit a57ff692eeab8d21c853dc1ddaf0164f517074c3
|
139
src/httpd.js.c
139
src/httpd.js.c
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user