js: Move /view to C.
This commit is contained in:
parent
e638b155a1
commit
72def5ae6d
61
core/core.js
61
core/core.js
@ -962,66 +962,7 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
}
|
||||
|
||||
let process;
|
||||
if (uri == '/view') {
|
||||
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') {
|
||||
if (uri == '/save') {
|
||||
let match;
|
||||
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
|
||||
let user = match[1];
|
||||
|
120
src/httpd.js.c
120
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 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);
|
||||
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_request_class_id;
|
||||
@ -959,6 +961,123 @@ static void _httpd_endpoint_static(tf_http_request_t* 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",
|
||||
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)
|
||||
{
|
||||
tf_http_request_t* request = user_data;
|
||||
@ -1690,6 +1809,7 @@ void tf_httpd_register(JSContext* context)
|
||||
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, "/&*.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, "/debug", _httpd_endpoint_debug, NULL, task);
|
||||
|
Loading…
Reference in New Issue
Block a user