forked from cory/tildefriends
js: Move /save to C.
This commit is contained in:
parent
01b8c209de
commit
863e50203e
211
core/core.js
211
core/core.js
@ -932,148 +932,83 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
}
|
||||
|
||||
let process;
|
||||
if (uri == '/save') {
|
||||
let match;
|
||||
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
|
||||
let user = match[1];
|
||||
let appName = match[2];
|
||||
let credentials = await httpd.auth_query(request.headers);
|
||||
if (
|
||||
credentials &&
|
||||
credentials.session &&
|
||||
(credentials.session.name == user ||
|
||||
(credentials.permissions.administration && user == 'core'))
|
||||
) {
|
||||
let database = new Database(user);
|
||||
let data;
|
||||
let match;
|
||||
let id;
|
||||
let app_id = blobId;
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
|
||||
packageOwner = match[1];
|
||||
packageName = match[2];
|
||||
let db = new Database(match[1]);
|
||||
app_id = await db.get('path:' + match[2]);
|
||||
}
|
||||
|
||||
let app_object = JSON.parse(utf8Decode(request.body));
|
||||
let previous_id = await database.get('path:' + appName);
|
||||
if (previous_id) {
|
||||
try {
|
||||
let previous_object = JSON.parse(
|
||||
utf8Decode(await ssb.blobGet(previous_id))
|
||||
);
|
||||
delete previous_object.previous;
|
||||
delete app_object.previous;
|
||||
if (JSON.stringify(previous_object) == JSON.stringify(app_object)) {
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
});
|
||||
response.end('/' + previous_id);
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
app_object.previous = previous_id;
|
||||
let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
|
||||
|
||||
let apps = new Set();
|
||||
let apps_original = await database.get('apps');
|
||||
try {
|
||||
apps = new Set(JSON.parse(apps_original));
|
||||
} catch {}
|
||||
if (!apps.has(appName)) {
|
||||
apps.add(appName);
|
||||
}
|
||||
apps = JSON.stringify([...apps].sort());
|
||||
if (apps != apps_original) {
|
||||
await database.set('apps', apps);
|
||||
}
|
||||
await database.set('path:' + appName, newBlobId);
|
||||
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
|
||||
response.end('/' + newBlobId);
|
||||
} else {
|
||||
response.writeHead(401, {'Content-Type': 'text/plain; charset=utf-8'});
|
||||
response.end('401 Unauthorized');
|
||||
return;
|
||||
}
|
||||
} else if (blobId === '') {
|
||||
let newBlobId = await ssb.blobStore(request.body);
|
||||
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
|
||||
response.end('/' + newBlobId);
|
||||
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id)));
|
||||
id = app_object?.files[uri.substring(1)];
|
||||
if (!id && app_object?.files['handler.js']) {
|
||||
let answer;
|
||||
try {
|
||||
answer = await useAppHandler(
|
||||
response,
|
||||
app_id,
|
||||
uri.substring(1),
|
||||
request.query ? form.decodeForm(request.query) : undefined,
|
||||
request.headers,
|
||||
packageOwner,
|
||||
packageName
|
||||
);
|
||||
} catch (error) {
|
||||
data = utf8Encode(
|
||||
`Internal Server Error\n\n${error?.message}\n${error?.stack}`
|
||||
);
|
||||
response.writeHead(500, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Content-Length': data.length,
|
||||
});
|
||||
response.end(data);
|
||||
return;
|
||||
}
|
||||
if (answer && typeof answer.data == 'string') {
|
||||
answer.data = utf8Encode(answer.data);
|
||||
}
|
||||
sendData(
|
||||
response,
|
||||
answer?.data,
|
||||
answer?.content_type,
|
||||
Object.assign(answer?.headers ?? {}, {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
}),
|
||||
answer.status_code
|
||||
);
|
||||
} else if (id) {
|
||||
if (
|
||||
request.headers['if-none-match'] &&
|
||||
request.headers['if-none-match'] == '"' + id + '"'
|
||||
) {
|
||||
let headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
'Content-Length': '0',
|
||||
};
|
||||
response.writeHead(304, headers);
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'});
|
||||
response.end('Invalid name.');
|
||||
let headers = {
|
||||
ETag: '"' + id + '"',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
};
|
||||
data = await ssb.blobGet(id);
|
||||
let type =
|
||||
httpd.mime_type_from_extension(uri) ||
|
||||
httpd.mime_type_from_magic_bytes(data);
|
||||
sendData(response, data, type, headers);
|
||||
}
|
||||
} else {
|
||||
let data;
|
||||
let match;
|
||||
let id;
|
||||
let app_id = blobId;
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
|
||||
packageOwner = match[1];
|
||||
packageName = match[2];
|
||||
let db = new Database(match[1]);
|
||||
app_id = await db.get('path:' + match[2]);
|
||||
}
|
||||
|
||||
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id)));
|
||||
id = app_object?.files[uri.substring(1)];
|
||||
if (!id && app_object?.files['handler.js']) {
|
||||
let answer;
|
||||
try {
|
||||
answer = await useAppHandler(
|
||||
response,
|
||||
app_id,
|
||||
uri.substring(1),
|
||||
request.query ? form.decodeForm(request.query) : undefined,
|
||||
request.headers,
|
||||
packageOwner,
|
||||
packageName
|
||||
);
|
||||
} catch (error) {
|
||||
data = utf8Encode(
|
||||
`Internal Server Error\n\n${error?.message}\n${error?.stack}`
|
||||
);
|
||||
response.writeHead(500, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Content-Length': data.length,
|
||||
});
|
||||
response.end(data);
|
||||
return;
|
||||
}
|
||||
if (answer && typeof answer.data == 'string') {
|
||||
answer.data = utf8Encode(answer.data);
|
||||
}
|
||||
sendData(
|
||||
response,
|
||||
answer?.data,
|
||||
answer?.content_type,
|
||||
Object.assign(answer?.headers ?? {}, {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
}),
|
||||
answer.status_code
|
||||
);
|
||||
} else if (id) {
|
||||
if (
|
||||
request.headers['if-none-match'] &&
|
||||
request.headers['if-none-match'] == '"' + id + '"'
|
||||
) {
|
||||
let headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
'Content-Length': '0',
|
||||
};
|
||||
response.writeHead(304, headers);
|
||||
response.end();
|
||||
} else {
|
||||
let headers = {
|
||||
ETag: '"' + id + '"',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
};
|
||||
data = await ssb.blobGet(id);
|
||||
let type =
|
||||
httpd.mime_type_from_extension(uri) ||
|
||||
httpd.mime_type_from_magic_bytes(data);
|
||||
sendData(response, data, type, headers);
|
||||
}
|
||||
} else {
|
||||
sendData(response, data, undefined, {});
|
||||
}
|
||||
sendData(response, data, undefined, {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,6 +407,11 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
|
||||
|
||||
if (connection->body_length == connection->content_length)
|
||||
{
|
||||
/* Null-terminate for convenience. */
|
||||
if (connection->body)
|
||||
{
|
||||
((char*)connection->body)[connection->body_length] = '\0';
|
||||
}
|
||||
tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t));
|
||||
*request = (tf_http_request_t) {
|
||||
.http = connection->http,
|
||||
@ -500,7 +505,7 @@ static size_t _http_on_read_plain_internal(tf_http_connection_t* connection, con
|
||||
|
||||
if (connection->content_length)
|
||||
{
|
||||
connection->body = tf_realloc(connection->body, connection->content_length);
|
||||
connection->body = tf_realloc(connection->body, connection->content_length + 1);
|
||||
}
|
||||
|
||||
if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->trace_name, &connection->user_data) || !connection->callback)
|
||||
|
274
src/httpd.js.c
274
src/httpd.js.c
@ -966,6 +966,56 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
|
||||
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
|
||||
}
|
||||
|
||||
typedef struct _user_app_t
|
||||
{
|
||||
const char* user;
|
||||
const char* app;
|
||||
} user_app_t;
|
||||
|
||||
static user_app_t* _parse_user_app_from_path(const char* path, const char* expected_suffix)
|
||||
{
|
||||
if (!path || path[0] != '/' || path[1] != '~')
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t length = strlen(path);
|
||||
size_t suffix_length = expected_suffix ? strlen(expected_suffix) : 0;
|
||||
if (length < suffix_length || strcmp(path + length - suffix_length, expected_suffix) != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* slash = strchr(path + 2, '/');
|
||||
if (!slash)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* user = path + 2;
|
||||
size_t user_length = (size_t)(slash - user);
|
||||
const char* app = slash + 1;
|
||||
size_t app_length = (size_t)(length - suffix_length - user_length - 3);
|
||||
user_app_t* result = tf_malloc(sizeof(user_app_t) + user_length + 1 + app_length + 1);
|
||||
|
||||
*result = (user_app_t) {
|
||||
.user = (char*)(result + 1),
|
||||
.app = (char*)(result + 1) + user_length + 1,
|
||||
};
|
||||
memcpy((char*)result->user, user, user_length);
|
||||
((char*)result->user)[user_length] = '\0';
|
||||
memcpy((char*)result->app, app, app_length);
|
||||
((char*)result->app)[app_length] = '\0';
|
||||
|
||||
if (!_is_name_valid(result->user) || !_is_name_valid(result->app))
|
||||
{
|
||||
tf_free(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _view_t
|
||||
{
|
||||
tf_http_request_t* request;
|
||||
@ -996,24 +1046,23 @@ 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] == '~')
|
||||
|
||||
user_app_t* user_app = _parse_user_app_from_path(request->path, "/view");
|
||||
if (user_app)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
snprintf(blob_id, sizeof(blob_id), "%s", 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)
|
||||
{
|
||||
@ -1082,6 +1131,162 @@ static void _httpd_endpoint_view(tf_http_request_t* request)
|
||||
tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view);
|
||||
}
|
||||
|
||||
typedef struct _save_t
|
||||
{
|
||||
tf_http_request_t* request;
|
||||
int response;
|
||||
char blob_id[256];
|
||||
} save_t;
|
||||
|
||||
static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
save_t* save = user_data;
|
||||
tf_http_request_t* request = save->request;
|
||||
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||
|
||||
JSMallocFunctions funcs = { 0 };
|
||||
tf_get_js_malloc_functions(&funcs);
|
||||
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||
JSContext* context = JS_NewContext(runtime);
|
||||
|
||||
JSValue jwt = _authenticate_jwt(ssb, context, session);
|
||||
JSValue user = JS_GetPropertyStr(context, jwt, "name");
|
||||
const char* user_string = JS_ToCString(context, user);
|
||||
|
||||
if (user_string && _is_name_valid(user_string))
|
||||
{
|
||||
user_app_t* user_app = _parse_user_app_from_path(request->path, "/save");
|
||||
if (user_app)
|
||||
{
|
||||
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, user_string, "administration")))
|
||||
{
|
||||
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||
char* app_path = tf_malloc(path_length);
|
||||
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||
|
||||
const char* old_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||
|
||||
JSValue new_app = JS_ParseJSON(context, request->body, request->content_length, NULL);
|
||||
tf_util_report_error(context, new_app);
|
||||
if (JS_IsObject(new_app))
|
||||
{
|
||||
uint8_t* old_blob = NULL;
|
||||
size_t old_blob_size = 0;
|
||||
if (tf_ssb_db_blob_get(ssb, old_blob_id, &old_blob, &old_blob_size))
|
||||
{
|
||||
JSValue old_app = JS_ParseJSON(context, (const char*)old_blob, old_blob_size, NULL);
|
||||
if (JS_IsObject(old_app))
|
||||
{
|
||||
JSAtom previous = JS_NewAtom(context, "previous");
|
||||
JS_DeleteProperty(context, old_app, previous, 0);
|
||||
JS_DeleteProperty(context, new_app, previous, 0);
|
||||
|
||||
JSValue old_app_json = JS_JSONStringify(context, old_app, JS_NULL, JS_NULL);
|
||||
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||
const char* old_app_str = JS_ToCString(context, old_app_json);
|
||||
const char* new_app_str = JS_ToCString(context, new_app_json);
|
||||
|
||||
if (old_app_str && new_app_str && strcmp(old_app_str, new_app_str) == 0)
|
||||
{
|
||||
snprintf(save->blob_id, sizeof(save->blob_id), "/%s", old_blob_id);
|
||||
save->response = 200;
|
||||
}
|
||||
|
||||
JS_FreeCString(context, old_app_str);
|
||||
JS_FreeCString(context, new_app_str);
|
||||
JS_FreeValue(context, old_app_json);
|
||||
JS_FreeValue(context, new_app_json);
|
||||
JS_FreeAtom(context, previous);
|
||||
}
|
||||
JS_FreeValue(context, old_app);
|
||||
tf_free(old_blob);
|
||||
}
|
||||
|
||||
if (!save->response)
|
||||
{
|
||||
if (old_blob_id)
|
||||
{
|
||||
JS_SetPropertyStr(context, new_app, "previous", JS_NewString(context, old_blob_id));
|
||||
}
|
||||
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||
size_t new_app_length = 0;
|
||||
const char* new_app_str = JS_ToCStringLen(context, &new_app_length, new_app_json);
|
||||
|
||||
char blob_id[250] = { 0 };
|
||||
if (tf_ssb_db_blob_store(ssb, (const uint8_t*)new_app_str, new_app_length, blob_id, sizeof(blob_id), NULL) &&
|
||||
tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id))
|
||||
{
|
||||
tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app);
|
||||
snprintf(save->blob_id, sizeof(save->blob_id), "/%s", blob_id);
|
||||
save->response = 200;
|
||||
}
|
||||
|
||||
JS_FreeCString(context, new_app_str);
|
||||
JS_FreeValue(context, new_app_json);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
save->response = 400;
|
||||
}
|
||||
JS_FreeValue(context, new_app);
|
||||
|
||||
tf_free(app_path);
|
||||
tf_free((void*)old_blob_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
save->response = 401;
|
||||
}
|
||||
tf_free(user_app);
|
||||
}
|
||||
else if (strcmp(request->path, "/save") == 0)
|
||||
{
|
||||
char blob_id[250] = { 0 };
|
||||
if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL))
|
||||
{
|
||||
snprintf(save->blob_id, sizeof(save->blob_id), "/%s", blob_id);
|
||||
save->response = 200;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
save->response = 400;
|
||||
}
|
||||
}
|
||||
|
||||
tf_free((void*)session);
|
||||
JS_FreeCString(context, user_string);
|
||||
JS_FreeValue(context, user);
|
||||
JS_FreeValue(context, jwt);
|
||||
JS_FreeContext(context);
|
||||
JS_FreeRuntime(runtime);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_save_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
save_t* save = user_data;
|
||||
tf_http_request_t* request = save->request;
|
||||
if (*save->blob_id)
|
||||
{
|
||||
tf_http_respond(request, 200, NULL, 0, save->blob_id, strlen(save->blob_id));
|
||||
}
|
||||
tf_http_request_unref(request);
|
||||
tf_free(save);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_save(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);
|
||||
save_t* save = tf_malloc(sizeof(save_t));
|
||||
*save = (save_t) {
|
||||
.request = request,
|
||||
};
|
||||
tf_ssb_run_work(ssb, _httpd_endpoint_save_work, _httpd_endpoint_save_after_work, save);
|
||||
}
|
||||
|
||||
typedef struct _delete_t
|
||||
{
|
||||
tf_http_request_t* request;
|
||||
@ -1104,32 +1309,31 @@ static void _httpd_endpoint_delete_work(tf_ssb_t* ssb, void* user_data)
|
||||
const char* user_string = JS_ToCString(context, user);
|
||||
if (user_string && _is_name_valid(user_string))
|
||||
{
|
||||
size_t length = strlen(user_string);
|
||||
if (request->path && request->path[0] == '/' && request->path[1] == '~' &&
|
||||
(strncmp(request->path + 2, user_string, length) == 0 ||
|
||||
(strncmp(request->path + 2, "core", strlen("core")) == 0 && tf_ssb_db_user_has_permission(ssb, user_string, "administration"))) &&
|
||||
request->path[2 + length] == '/')
|
||||
user_app_t* user_app = _parse_user_app_from_path(request->path, "/delete");
|
||||
if (user_app)
|
||||
{
|
||||
char* app_name = tf_strdup(request->path + 2 + length + 1);
|
||||
if (app_name)
|
||||
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, user_string, "administration")))
|
||||
{
|
||||
if (strlen(app_name) > strlen("/delete") && strcmp(app_name + strlen(app_name) - strlen("/delete"), "/delete") == 0)
|
||||
{
|
||||
app_name[strlen(app_name) - strlen("/delete")] = '\0';
|
||||
}
|
||||
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||
char* app_path = tf_malloc(path_length);
|
||||
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||
|
||||
bool changed = false;
|
||||
changed = tf_ssb_db_remove_value_from_array_property(ssb, user_string, "apps", user_app->app) || changed;
|
||||
changed = tf_ssb_db_remove_property(ssb, user_string, app_path) || changed;
|
||||
delete->response = changed ? 200 : 404;
|
||||
tf_free(app_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete->response = 401;
|
||||
}
|
||||
|
||||
size_t path_length = strlen("path:") + strlen(app_name) + 1;
|
||||
char* app_path = tf_malloc(path_length);
|
||||
snprintf(app_path, path_length, "path:%s", app_name);
|
||||
|
||||
bool changed = false;
|
||||
changed = tf_ssb_db_remove_value_from_array_property(ssb, user_string, "apps", app_name) || changed;
|
||||
changed = tf_ssb_db_remove_property(ssb, user_string, app_path) || changed;
|
||||
delete->response = changed ? 200 : 404;
|
||||
tf_free(app_name);
|
||||
tf_free(app_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete->response = 404;
|
||||
}
|
||||
tf_free(user_app);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1898,7 +2102,9 @@ void tf_httpd_register(JSContext* context)
|
||||
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, "/~*/*/save", _httpd_endpoint_save, NULL, task);
|
||||
tf_http_add_handler(http, "/~*/*/delete", _httpd_endpoint_delete, NULL, task);
|
||||
tf_http_add_handler(http, "/save", _httpd_endpoint_save, 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);
|
||||
|
21
src/ssb.db.c
21
src/ssb.db.c
@ -1825,6 +1825,27 @@ bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, c
|
||||
return result;
|
||||
}
|
||||
|
||||
bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value)
|
||||
{
|
||||
bool result = false;
|
||||
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||
sqlite3_stmt* statement = NULL;
|
||||
if (sqlite3_prepare(db,
|
||||
"INSERT INTO properties (id, key, value) VALUES (?1, ?2, json_array(?3)) ON CONFLICT DO UPDATE SET value = json_insert(properties.value, '$[#]', ?3) WHERE "
|
||||
"properties.id = ?1 AND properties.key = ?2 AND NOT EXISTS (SELECT 1 FROM json_each(properties.value) AS entry WHERE entry.value = ?3)",
|
||||
-1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, value, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
result = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) != 0;
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
tf_ssb_release_db_writer(ssb, db);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* package_owner, const char* package_name, char* out_identity, size_t out_identity_size)
|
||||
{
|
||||
sqlite3_stmt* statement = NULL;
|
||||
|
10
src/ssb.db.h
10
src/ssb.db.h
@ -418,6 +418,16 @@ bool tf_ssb_db_remove_property(tf_ssb_t* ssb, const char* id, const char* key);
|
||||
*/
|
||||
bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);
|
||||
|
||||
/**
|
||||
** Ensure a value is in an entry in the properties table that is a JSON array.
|
||||
** @param ssb The SSB instance.
|
||||
** @param id The user.
|
||||
** @param key The property key.
|
||||
** @param value The value to add to the JSON array.
|
||||
** @return true if the property was updated.
|
||||
*/
|
||||
bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);
|
||||
|
||||
/**
|
||||
** Resolve a hostname to its index path by global settings.
|
||||
** @param ssb The SSB instance.
|
||||
|
@ -158,6 +158,29 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
||||
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
|
||||
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
|
||||
|
||||
const char* value = tf_ssb_db_get_property(ssb0, "user", "array");
|
||||
assert(value == NULL);
|
||||
assert(tf_ssb_db_add_value_to_array_property(ssb0, "user", "array", "1") == true);
|
||||
value = tf_ssb_db_get_property(ssb0, "user", "array");
|
||||
assert(strcmp(value, "[\"1\"]") == 0);
|
||||
tf_free((void*)value);
|
||||
assert(tf_ssb_db_add_value_to_array_property(ssb0, "user", "array", "2") == true);
|
||||
value = tf_ssb_db_get_property(ssb0, "user", "array");
|
||||
assert(strcmp(value, "[\"1\",\"2\"]") == 0);
|
||||
tf_free((void*)value);
|
||||
assert(tf_ssb_db_add_value_to_array_property(ssb0, "user", "array", "1") == false);
|
||||
assert(tf_ssb_db_add_value_to_array_property(ssb0, "user", "array", "2") == false);
|
||||
value = tf_ssb_db_get_property(ssb0, "user", "array");
|
||||
assert(strcmp(value, "[\"1\",\"2\"]") == 0);
|
||||
tf_free((void*)value);
|
||||
assert(tf_ssb_db_remove_value_from_array_property(ssb0, "user", "array", "1") == true);
|
||||
assert(tf_ssb_db_remove_value_from_array_property(ssb0, "user", "array", "1") == false);
|
||||
assert(tf_ssb_db_remove_value_from_array_property(ssb0, "user", "array", "2") == true);
|
||||
assert(tf_ssb_db_remove_value_from_array_property(ssb0, "user", "array", "2") == false);
|
||||
value = tf_ssb_db_get_property(ssb0, "user", "array");
|
||||
assert(strcmp(value, "[]") == 0);
|
||||
tf_free((void*)value);
|
||||
|
||||
uv_idle_t idle0 = { .data = ssb0 };
|
||||
uv_idle_init(&loop, &idle0);
|
||||
uv_idle_start(&idle0, _ssb_test_idle);
|
||||
|
Loading…
Reference in New Issue
Block a user