forked from cory/tildefriends
141 lines
3.9 KiB
C
141 lines
3.9 KiB
C
#include "httpd.js.h"
|
|
|
|
#include "http.h"
|
|
#include "mem.h"
|
|
#include "ssb.db.h"
|
|
#include "ssb.h"
|
|
#include "task.h"
|
|
#include "util.js.h"
|
|
|
|
typedef struct _view_t
|
|
{
|
|
tf_http_request_t* request;
|
|
const char** form_data;
|
|
void* data;
|
|
size_t size;
|
|
char etag[256];
|
|
char notify_want_blob_id[k_blob_id_len];
|
|
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[k_blob_id_len] = "";
|
|
|
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/view");
|
|
if (user_app)
|
|
{
|
|
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
|
|
char* app_path = tf_malloc(app_path_length);
|
|
snprintf(app_path, app_path_length, "path:%s", user_app->app);
|
|
const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
|
tf_string_set(blob_id, sizeof(blob_id), value);
|
|
tf_free(app_path);
|
|
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);
|
|
}
|
|
tf_free(user_app);
|
|
|
|
if (*blob_id)
|
|
{
|
|
snprintf(view->etag, sizeof(view->etag), "\"%s\"", 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) == 0)
|
|
{
|
|
view->not_modified = true;
|
|
}
|
|
else
|
|
{
|
|
if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
|
|
{
|
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
|
tf_ssb_db_add_blob_wants(db, blob_id);
|
|
tf_ssb_release_db_writer(ssb, db);
|
|
tf_string_set(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), blob_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
{
|
|
view_t* view = user_data;
|
|
const char* filename = tf_httpd_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 ? tf_httpd_magic_bytes_to_content_type(view->data, view->size) : "text/plain",
|
|
"etag",
|
|
view->etag,
|
|
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));
|
|
}
|
|
|
|
if (*view->notify_want_blob_id)
|
|
{
|
|
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
|
|
}
|
|
|
|
tf_free(view->form_data);
|
|
tf_http_request_unref(view->request);
|
|
tf_free(view);
|
|
}
|
|
|
|
void tf_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 = tf_httpd_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);
|
|
}
|