From 78745e06f4a4365e9caa8ee12fb0d9e15fb59300 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Tue, 17 Feb 2026 23:34:53 +0100 Subject: Simple client CRUD --- src/web.c | 300 +++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 201 insertions(+), 99 deletions(-) (limited to 'src/web.c') diff --git a/src/web.c b/src/web.c index 810d5d0..8c37bfe 100644 --- a/src/web.c +++ b/src/web.c @@ -22,16 +22,6 @@ static void sqliteError(void *pArg, int iErrCode, const char *zMsg) { fprintf(stderr, "(%d) %s\n", iErrCode, zMsg); } -struct PostCollector { - char *data; - size_t size; - size_t capacity; -}; - -struct Request { - struct Server *server; - struct PostCollector pp; -}; void prepare_database(struct App *app) { // @CLEANUP: Move this to main? doesn't belong here at least @@ -310,14 +300,12 @@ int submit_report(struct App *app, char *client, enum ReportEvent event) { } } + if(sqlite3_close(conn) != SQLITE_OK) { + abort(); + } return 0; } -struct CreateClientResult { - int64_t id; - char* name; -}; - int create_client(struct Request *req, char *name, struct CreateClientResult *result) { sqlite3 *conn; int err; @@ -345,23 +333,43 @@ int create_client(struct Request *req, char *name, struct CreateClientResult *re } } + if(sqlite3_close(conn) != SQLITE_OK) { + abort(); + } return 0; } -int list_clients(struct Request *req, char **response, size_t *response_len) { +int delete_client(struct Request *req, int64_t client_id) { sqlite3 *conn; - int err; - if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { + if(sqlite3_open(req->server->app->dbname, &conn) != SQLITE_OK) { abort(); } - *response = malloc(4096); - char *cursor = *response; - size_t written; + sqlite3_stmt *stmt; + if(sqlite3_prepare_v2(conn, "DELETE FROM clients WHERE id = ?1", -1, &stmt, NULL) != SQLITE_OK) { + abort(); + } + if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { + abort(); + } + if(sqlite3_step(stmt) != SQLITE_DONE) { + abort(); + } + if(sqlite3_finalize(stmt) != SQLITE_OK) { + abort(); + } + if(sqlite3_close(conn) != SQLITE_OK) { + abort(); + } + return 0; +} - written = snprintf(cursor, *response + 4096 - cursor, "{ \"page\": [ "); - if(written >= *response + 4096 - cursor) abort(); - cursor += written; +int list_clients(struct Request *req, struct ListClientResult *result) { + sqlite3 *conn; + int err; + if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { + abort(); + } { sqlite3_stmt *stmt; @@ -387,9 +395,8 @@ int list_clients(struct Request *req, char **response, size_t *response_len) { int64_t clientId = sqlite3_column_int64(stmt, 0); const uint8_t *name = sqlite3_column_text(stmt, 1); - written = snprintf(cursor, *response + 4096 - cursor, "{ \"id\": \"%ld\", \"name\": \"%s\" }, ", clientId, name); - if(written >= *response + 4096 - cursor) abort(); - cursor += written; + result->clients[index].id = clientId; + strncpy(result->clients[index].name, (char*)name, CLIENT_NAME_MAX); index++; } @@ -397,42 +404,77 @@ int list_clients(struct Request *req, char **response, size_t *response_len) { abort(); } - // Remove the last comma and space - if(index > 0) cursor -= 2; - - written = snprintf(cursor, *response + 4096 - cursor, " ] }"); - if(written >= *response + 4096 - cursor) abort(); - cursor += written; - - *response_len = cursor - *response; + result->clients_len = index; if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } + if(sqlite3_close(conn) != SQLITE_OK) { + abort(); + } return 0; } -static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) { - FILE *f = cls; +static char *render_clients_page(struct Request *request, size_t *out_len) { + int ret; + struct ListClientResult *result = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16); + ret = list_clients(request, result); + if(ret != 0) { + free(result); + return NULL; + } - fseek(f, pos, SEEK_SET); - size_t read = fread(buf, 1, max, f); - if(read > 0) { - return read; + char *buf = NULL; + size_t buf_len = 0; + FILE *f = open_memstream(&buf, &buf_len); + if (!f) { + free(result); + return NULL; } - if(feof(f)) { - return MHD_CONTENT_READER_END_OF_STREAM; + fprintf(f, + "\n" + "\n" + "\n" + " Clients\n" + "\n" + "\n" + "

Clients

\n" + " \n" + " \n"); + + for (int i = 0; i < result->clients_len; i++) { + fprintf(f, + " \n" + " \n" + " \n" + " \n", + result->clients[i].name, + result->clients[i].id + ); } - abort(); -} + fprintf(f, + "
NameActions
%s\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + "

Add New Client

\n" + "
\n" + " \n" + " \n" + "
\n" + "\n" + "\n"); -static void file_close(void *cls) { - FILE *f = cls; fclose(f); + *out_len = buf_len; + free(result); + return buf; } static enum MHD_Result create_post_collector(struct PostCollector *c) { @@ -466,6 +508,34 @@ static enum MHD_Result destroy_post_collector(struct PostCollector *c) { return MHD_YES; } +static enum MHD_Result +form_iterator(void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *filename, + const char *content_type, + const char *transfer_encoding, + const char *data, + uint64_t off, + size_t size) +{ + struct Request *request = cls; + + if (strcmp(key, "name") == 0 && off + size < CLIENT_NAME_MAX) { + memcpy(request->form.name + off, data, size); + request->form.name[off + size] = '\0'; + request->form.name_set = 1; + } else if (strcmp(key, "client_id") == 0 && off == 0) { + char buf[32] = {0}; + size_t copy_size = size < 31 ? size : 31; + memcpy(buf, data, copy_size); + request->form.client_id = strtoll(buf, NULL, 10); + request->form.client_id_set = 1; + } + + return MHD_YES; +} + static enum MHD_Result handler( void *cls, struct MHD_Connection *connection, @@ -492,20 +562,75 @@ static enum MHD_Result handler( request->server = server; if(strcmp(method, "POST") == 0) { - create_post_collector(&request->pp); + if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0) { + request->post_proc = MHD_create_post_processor( + connection, 1024, &form_iterator, request); + if (request->post_proc == NULL) { + free(request); + return MHD_NO; + } + } else { + create_post_collector(&request->pp); + } } return MHD_YES; } if(strcmp(method, "POST") == 0) { - collect_post(&request->pp, upload_data, upload_data_size); - if (*upload_data_size != 0) { - *upload_data_size = 0; - return MHD_YES; + if (request->post_proc != NULL) { + if (*upload_data_size != 0) { + MHD_post_process(request->post_proc, upload_data, *upload_data_size); + *upload_data_size = 0; + return MHD_YES; + } + } else { + collect_post(&request->pp, upload_data, upload_data_size); + if (*upload_data_size != 0) { + *upload_data_size = 0; + return MHD_YES; + } } - if(strcmp(url, "/api/report") == 0) { + if(strcmp(url, "/clients") == 0) { + MHD_destroy_post_processor(request->post_proc); + request->post_proc = NULL; + + if (!request->form.name_set || request->form.name[0] == '\0') { + return MHD_NO; + } + + struct CreateClientResult result; + ret = create_client(request, request->form.name, &result); + if(ret != 0) return MHD_NO; + + // Post-Redirect-Get: redirect back to GET /clients + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + ret = MHD_add_response_header(response, "Location", "/clients"); + if(ret != MHD_YES) return ret; + ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/clients/delete") == 0) { + MHD_destroy_post_processor(request->post_proc); + request->post_proc = NULL; + + if (!request->form.client_id_set || request->form.client_id <= 0) { + return MHD_NO; + } + + ret = delete_client(request, request->form.client_id); + if(ret != 0) return MHD_NO; + + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + ret = MHD_add_response_header(response, "Location", "/clients"); + if(ret != MHD_YES) return ret; + ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/api/report") == 0) { cJSON *rootJson = cJSON_ParseWithLength(request->pp.data, request->pp.size); @@ -613,21 +738,20 @@ static enum MHD_Result handler( } else if (strcmp(method, "GET") == 0) { if (*upload_data_size != 0) return MHD_NO; - if(strcmp(url, "/api/client") == 0) { - char *body; - size_t body_len; - ret = list_clients(request, &body, &body_len); - if(ret != 0) { - // Should actually return an error page, but this will do for now + if(strcmp(url, "/clients") == 0) { + size_t html_len; + char *html = render_clients_page(request, &html_len); + + if (!html) { return MHD_NO; } struct MHD_Response *response = MHD_create_response_from_buffer( - body_len, - body, + html_len, + html, MHD_RESPMEM_MUST_FREE ); - ret = MHD_add_response_header(response, "Content-Type", "application/json"); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return ret; ret = MHD_queue_response( connection, @@ -637,44 +761,19 @@ static enum MHD_Result handler( MHD_destroy_response(response); return ret; } else { - // @SECURITY: We should restrict this lookup. Look at openat2(2) - char path[512]; - if(snprintf(path, sizeof(path), "static/%s", url) >= sizeof(path)) abort(); - struct stat statbuf; - FILE *f = NULL; - if(stat(path, &statbuf) == 0 && (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode))) { - f = fopen(path, "rb"); - } - - if(f != NULL) { - struct MHD_Response *response = MHD_create_response_from_callback( - statbuf.st_size, statbuf.st_blksize, - &file_reader, f, - &file_close - ); - - ret = MHD_queue_response( - connection, - MHD_HTTP_OK, - response - ); - MHD_destroy_response(response); - return ret; - } else { - struct MHD_Response *response = MHD_create_response_from_buffer( - strlen(server->page), - (void *)server->page, - MHD_RESPMEM_PERSISTENT - ); + struct MHD_Response *response = MHD_create_response_from_buffer( + strlen(server->page), + (void *)server->page, + MHD_RESPMEM_PERSISTENT + ); - ret = MHD_queue_response( - connection, - MHD_HTTP_OK, - response - ); - MHD_destroy_response(response); - return ret; - } + ret = MHD_queue_response( + connection, + MHD_HTTP_OK, + response + ); + MHD_destroy_response(response); + return ret; } } @@ -689,6 +788,9 @@ static void request_completed_callback ( ) { struct Request *request = *con_cls; + if (request->post_proc != NULL) { + MHD_destroy_post_processor(request->post_proc); + } destroy_post_collector(&request->pp); free(request); } -- cgit v1.2.3