summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesper Jensen <jesper@jnsn.dev>2026-02-21 19:55:14 +0100
committerJesper Jensen <jesper@jnsn.dev>2026-02-21 19:55:14 +0100
commit0f3cbe7c7a562d65e16b704a038e8e6868436170 (patch)
treee551ba9e5a57983a91332f501ebc6f1be087dbbd
parentcd59f14f9de567f2e84f88b3e70d4652ab1e5ea8 (diff)
Introduce new chain style handler processing
-rw-r--r--src/html.c2
-rw-r--r--src/web.c923
-rw-r--r--src/web.h15
-rw-r--r--test/http.c59
4 files changed, 523 insertions, 476 deletions
diff --git a/src/html.c b/src/html.c
index 228e8ef..482ca97 100644
--- a/src/html.c
+++ b/src/html.c
@@ -468,7 +468,7 @@ char *html_render_client_detail_page(const struct Request *request, const struct
" <details>\n"
" <summary>How To</summary>\n"
" <div>\n"
- " <pre><code>curl -s -X POST %s://%s \\\n"
+ " <pre><code>curl -s -X POST %s://%s/api/report \\\n"
" -H \"Content-Type: application/json\" \\\n"
" -d \'{\"client\": \"%s\", \"secret\": \"%s\", \"event\": \"begin_backup\"}\'"
"</pre></code>\n"
diff --git a/src/web.c b/src/web.c
index 61393ad..adb98ad 100644
--- a/src/web.c
+++ b/src/web.c
@@ -120,6 +120,491 @@ form_iterator(void *cls,
return MHD_YES;
}
+static enum HandlerStatus handle_logout(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/logout") != 0)
+ return HANDLER_DECLINE;
+
+ if (strcmp(request->method, "POST") != 0)
+ return HANDLER_DECLINE;
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Set-Cookie", "session=; Path=/; HttpOnly; Max-Age=0");
+ MHD_add_response_header(response, "Location", "/login");
+ enum MHD_Result ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
+ MHD_destroy_response(response);
+ return ret == MHD_YES ? HANDLER_ACCEPTED : HANDLER_ERROR;
+}
+
+static enum HandlerStatus handle_login(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/login") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (strcmp(request->method, "POST") == 0) {
+ if (request->post_proc == NULL && !request->form.username_set) {
+ request->post_proc = MHD_create_post_processor(
+ connection, 1024, &form_iterator, request);
+ if (request->post_proc == NULL)
+ return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ MHD_destroy_post_processor(request->post_proc);
+ request->post_proc = NULL;
+
+ if (!request->form.username_set || !request->form.password_set) {
+ size_t html_len;
+ char *html = html_render_login_page("Invalid credentials", &html_len);
+ if (!html) return HANDLER_ERROR;
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ html_len, html, MHD_RESPMEM_MUST_FREE);
+ ret = MHD_add_response_header(response, "Content-Type", "text/html");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ if (!validate_credentials(server->app, request->form.username, request->form.password)) {
+ size_t html_len;
+ char *html = html_render_login_page("Invalid credentials", &html_len);
+ if (!html) return HANDLER_ERROR;
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ html_len, html, MHD_RESPMEM_MUST_FREE);
+ ret = MHD_add_response_header(response, "Content-Type", "text/html");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ char *token = generate_session_token(server->app, request->form.username, request->form.password);
+ if (token == NULL) return HANDLER_ERROR;
+
+ char cookie_header[128];
+ snprintf(cookie_header, sizeof(cookie_header), "session=%s; Path=/; HttpOnly", token);
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Set-Cookie", cookie_header);
+ MHD_add_response_header(response, "Location", "/clients");
+ ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
+ MHD_destroy_response(response);
+ return ret == MHD_YES ? HANDLER_ACCEPTED : HANDLER_ERROR;
+ }
+
+ if (strcmp(request->method, "GET") == 0) {
+ size_t html_len;
+ char *html = html_render_login_page(NULL, &html_len);
+ if (!html) return HANDLER_ERROR;
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ html_len, html, MHD_RESPMEM_MUST_FREE);
+ ret = MHD_add_response_header(response, "Content-Type", "text/html");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ return HANDLER_DECLINE;
+}
+
+static enum HandlerStatus handle_clients(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/clients") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (strcmp(request->method, "POST") == 0) {
+ if (request->post_proc == NULL && !request->form.name_set) {
+ request->post_proc = MHD_create_post_processor(
+ connection, 1024, &form_iterator, request);
+ if (request->post_proc == NULL)
+ return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ MHD_destroy_post_processor(request->post_proc);
+ request->post_proc = NULL;
+
+ if (!is_authenticated(server->app, connection)) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Location", "/login");
+ ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ if (!request->form.name_set || request->form.name[0] == '\0') {
+ return HANDLER_ERROR;
+ }
+
+ struct CreateClientResult result;
+ ret = create_client(server->app, request->form.name, &result);
+ if(ret != 0) return HANDLER_ERROR;
+
+ // 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 HANDLER_ERROR;
+ ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ if (strcmp(request->method, "GET") == 0) {
+ if (!is_authenticated(server->app, connection)) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Location", "/login");
+ ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ struct ListClientResult *clients = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16);
+ ret = list_clients(server->app, clients);
+ if(ret != 0) {
+ free(clients);
+ return HANDLER_ERROR;
+ }
+
+ size_t html_len;
+ char *html = html_render_clients_page(request, clients, &html_len);
+ free(clients);
+
+ if (!html) {
+ return HANDLER_ERROR;
+ }
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ html_len,
+ html,
+ MHD_RESPMEM_MUST_FREE
+ );
+ ret = MHD_add_response_header(response, "Content-Type", "text/html");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(
+ connection,
+ MHD_HTTP_OK,
+ response
+ );
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ return HANDLER_DECLINE;
+}
+
+static enum HandlerStatus handle_clients_delete(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/clients/delete") != 0)
+ return HANDLER_DECLINE;
+
+ if (strcmp(request->method, "POST") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (request->post_proc == NULL && !request->form.client_id_set) {
+ request->post_proc = MHD_create_post_processor(
+ connection, 1024, &form_iterator, request);
+ if (request->post_proc == NULL)
+ return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ MHD_destroy_post_processor(request->post_proc);
+ request->post_proc = NULL;
+
+ if (!is_authenticated(server->app, connection)) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Location", "/login");
+ ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ if (!request->form.client_id_set || request->form.client_id <= 0) {
+ return HANDLER_ERROR;
+ }
+
+ ret = delete_client(server->app, request->form.client_id);
+ if(ret != 0) return HANDLER_ERROR;
+
+ 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 HANDLER_ERROR;
+ ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+}
+
+static enum HandlerStatus handle_api_report(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/api/report") != 0)
+ return HANDLER_DECLINE;
+
+ if (strcmp(request->method, "POST") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (request->pp.capacity == 0) {
+ create_post_collector(&request->pp);
+ return HANDLER_ACCEPTED;
+ }
+
+ cJSON *rootJson = cJSON_ParseWithLength(request->pp.data, request->pp.size);
+
+ cJSON *clientJson = cJSON_GetObjectItemCaseSensitive(rootJson, "client");
+ if(!cJSON_IsString(clientJson) || clientJson->valuestring == NULL) {
+ abort();
+ }
+
+ cJSON *secretJson = cJSON_GetObjectItemCaseSensitive(rootJson, "secret");
+ if(!cJSON_IsString(secretJson) || secretJson->valuestring == NULL) {
+ abort();
+ }
+
+ cJSON *eventJson = cJSON_GetObjectItemCaseSensitive(rootJson, "event");
+ if(!cJSON_IsString(eventJson) || eventJson->valuestring == NULL) {
+ abort();
+ }
+ int is_finish;
+ if(strcmp(eventJson->valuestring, "begin_backup") == 0) {
+ is_finish = 0;
+ } else if(strcmp(eventJson->valuestring, "end_backup") == 0) {
+ is_finish = 1;
+ } else {
+ abort();
+ }
+
+ ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, is_finish);
+ if(ret == -2) {
+ cJSON_Delete(rootJson);
+ destroy_post_collector(&request->pp);
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT
+ );
+ ret = MHD_add_response_header(response, "Content-Type", "application/json");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(
+ connection,
+ MHD_HTTP_UNAUTHORIZED,
+ response
+ );
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+ if(ret != 0) {
+ return HANDLER_ERROR;
+ }
+
+ cJSON_Delete(rootJson);
+ destroy_post_collector(&request->pp);
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT
+ );
+ ret = MHD_add_response_header(response, "Content-Type", "application/json");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ response
+ );
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+}
+
+static enum HandlerStatus handle_client_detail(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strncmp(request->url, "/client/", 8) != 0)
+ return HANDLER_DECLINE;
+
+ if (strcmp(request->method, "GET") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (!is_authenticated(server->app, connection)) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_add_response_header(response, "Location", "/login");
+ ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ int64_t client_id;
+ if (sscanf(request->url + 8, "%ld", &client_id) != 1 || client_id <= 0) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT);
+ ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ struct GetClientOnlyResult client_result;
+ ret = get_client(server->app, client_id, &client_result);
+ if(ret != 0) {
+ return HANDLER_ERROR;
+ }
+
+ if(!client_result.found) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT
+ );
+ ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ struct GetBackupsResult *backups_result = malloc(sizeof(struct GetBackupsResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY);
+ backups_result->backups_len = MAX_BACKUPS_DISPLAY;
+ ret = get_client_backups(server->app, client_id, backups_result);
+ if(ret != 0) {
+ free(backups_result);
+ return HANDLER_ERROR;
+ }
+
+ size_t html_len;
+ char *html = html_render_client_detail_page(request, &client_result, backups_result, &html_len);
+ free(backups_result);
+
+ if (!html) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT
+ );
+ ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ html_len,
+ html,
+ MHD_RESPMEM_MUST_FREE
+ );
+ ret = MHD_add_response_header(response, "Content-Type", "text/html");
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ ret = MHD_queue_response(
+ connection,
+ MHD_HTTP_OK,
+ response
+ );
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+}
+
+static enum HandlerStatus handle_root(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ if (strcmp(request->url, "/") != 0)
+ return HANDLER_DECLINE;
+
+ if (strcmp(request->method, "GET") != 0)
+ return HANDLER_DECLINE;
+
+ struct Server *server = request->server;
+ enum MHD_Result ret;
+
+ if (!is_authenticated(server->app, connection)) {
+ struct MHD_Response *response = MHD_create_response_from_buffer(
+ 0, "", MHD_RESPMEM_PERSISTENT
+ );
+ MHD_add_response_header(response, "Location", "/login");
+ ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
+ MHD_destroy_response(response);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+ }
+
+ 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);
+ if(ret != MHD_YES) return HANDLER_ERROR;
+ return HANDLER_ACCEPTED;
+}
+
+static enum HandlerStatus handle_not_found(
+ struct MHD_Connection *connection,
+ struct Request *request
+) {
+ // This handler MUST accept the request since it will be the last one in
+ // the chain
+ struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
+ MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
+ MHD_destroy_response(response);
+ return HANDLER_ACCEPTED;
+}
+
+static RouteHandler handlers[] = {
+ handle_logout,
+ handle_login,
+ handle_clients,
+ handle_clients_delete,
+ handle_api_report,
+ handle_client_detail,
+ handle_root,
+ handle_not_found, // Catch all
+};
+
+#define HANDLER_COUNT (sizeof(handlers) / sizeof(handlers[0]))
+
static enum MHD_Result handler(
void *cls,
struct MHD_Connection *connection,
@@ -131,10 +616,7 @@ static enum MHD_Result handler(
void **ptr
) {
struct Server *server = cls;
- int ret;
- // For the first call where con_cls points at NULL we have to create the
- // connection state
struct Request *request = *ptr;
if (request == NULL) {
request = calloc(1, sizeof(struct Request));
@@ -144,437 +626,34 @@ static enum MHD_Result handler(
*ptr = request;
request->server = server;
+ request->url = url;
+ request->method = method;
// @COMPL: The proxy might rewrite this, and we should support that
request->host = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Host");
// @COMPL: There are other headers we should look into
request->proto = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-Forwarded-Proto");
-
- if(strcmp(method, "POST") == 0) {
- if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0 || strcmp(url, "/login") == 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;
+ // Fall through - handlers will set up POST processing if needed
}
- if(strcmp(method, "POST") == 0) {
+ if (*upload_data_size != 0) {
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 {
+ MHD_post_process(request->post_proc, upload_data, *upload_data_size);
+ } else if (request->pp.capacity > 0) {
collect_post(&request->pp, upload_data, upload_data_size);
- if (*upload_data_size != 0) {
- *upload_data_size = 0;
- return MHD_YES;
- }
- }
-
- if(strcmp(url, "/logout") == 0) {
- struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Set-Cookie", "session=; Path=/; HttpOnly; Max-Age=0");
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
- MHD_destroy_response(response);
- return ret;
- } else if(strcmp(url, "/login") == 0) {
- MHD_destroy_post_processor(request->post_proc);
- request->post_proc = NULL;
-
- if (!request->form.username_set || !request->form.password_set) {
- size_t html_len;
- char *html = html_render_login_page("Invalid credentials", &html_len);
- if (!html) return MHD_NO;
- struct MHD_Response *response = MHD_create_response_from_buffer(
- html_len, html, MHD_RESPMEM_MUST_FREE);
- ret = MHD_add_response_header(response, "Content-Type", "text/html");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- if (!validate_credentials(server->app, request->form.username, request->form.password)) {
- size_t html_len;
- char *html = html_render_login_page("Invalid credentials", &html_len);
- if (!html) return MHD_NO;
- struct MHD_Response *response = MHD_create_response_from_buffer(
- html_len, html, MHD_RESPMEM_MUST_FREE);
- ret = MHD_add_response_header(response, "Content-Type", "text/html");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- // Valid credentials - set cookie and redirect
- char *token = generate_session_token(server->app, request->form.username, request->form.password);
- if (token == NULL) return MHD_NO;
-
- char cookie_header[128];
- snprintf(cookie_header, sizeof(cookie_header), "session=%s; Path=/; HttpOnly", token);
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Set-Cookie", cookie_header);
- MHD_add_response_header(response, "Location", "/clients");
- ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
- MHD_destroy_response(response);
- return ret;
- } else if(strcmp(url, "/clients") == 0) {
- MHD_destroy_post_processor(request->post_proc);
- request->post_proc = NULL;
-
- // Auth check
- if (!is_authenticated(server->app, connection)) {
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- if (!request->form.name_set || request->form.name[0] == '\0') {
- return MHD_NO;
- }
-
- struct CreateClientResult result;
- ret = create_client(server->app, 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;
-
- // Auth check
- if (!is_authenticated(server->app, connection)) {
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- if (!request->form.client_id_set || request->form.client_id <= 0) {
- return MHD_NO;
- }
-
- ret = delete_client(server->app, 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);
-
- cJSON *clientJson = cJSON_GetObjectItemCaseSensitive(rootJson, "client");
- if(!cJSON_IsString(clientJson) || clientJson->valuestring == NULL) {
- abort();
- }
-
- cJSON *secretJson = cJSON_GetObjectItemCaseSensitive(rootJson, "secret");
- if(!cJSON_IsString(secretJson) || secretJson->valuestring == NULL) {
- abort();
- }
-
- cJSON *eventJson = cJSON_GetObjectItemCaseSensitive(rootJson, "event");
- if(!cJSON_IsString(eventJson) || eventJson->valuestring == NULL) {
- abort();
- }
- int is_finish;
- if(strcmp(eventJson->valuestring, "begin_backup") == 0) {
- is_finish = 0;
- } else if(strcmp(eventJson->valuestring, "end_backup") == 0) {
- is_finish = 1;
- } else {
- abort();
- }
-
-
- ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, is_finish);
- if(ret == -2) {
- cJSON_Delete(rootJson);
- destroy_post_collector(&request->pp);
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0,
- "",
- MHD_RESPMEM_PERSISTENT
- );
- ret = MHD_add_response_header(response, "Content-Type", "application/json");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_UNAUTHORIZED,
- response
- );
- MHD_destroy_response(response);
- return ret;
- }
- if(ret != 0) {
- // Should actually return an error page, but this will do for now
- return MHD_NO;
- }
-
- cJSON_Delete(rootJson);
- // We've taken in all the data, now we can use it
- destroy_post_collector(&request->pp);
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0,
- "",
- MHD_RESPMEM_PERSISTENT
- );
- ret = MHD_add_response_header(response, "Content-Type", "application/json");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_NO_CONTENT,
- response
- );
- MHD_destroy_response(response);
- return ret;
- } else if(strcmp(url, "/api/client") == 0) {
- cJSON *rootJson = cJSON_ParseWithLength(request->pp.data, request->pp.size);
-
- cJSON *nameJson = cJSON_GetObjectItemCaseSensitive(rootJson, "name");
- if(!cJSON_IsString(nameJson) || nameJson->valuestring == NULL) {
- abort();
- }
-
- struct CreateClientResult result;
- ret = create_client(server->app, nameJson->valuestring, &result);
- if(ret != 0) {
- // Should actually return an error page, but this will do for now
- return MHD_NO;
- }
-
- cJSON_Delete(rootJson);
- // We've taken in all the data, now we can use it
- destroy_post_collector(&request->pp);
-
- char *buf = malloc(1024);
- size_t buf_len = snprintf(buf, 1024, "{ \"id\": \"%ld\", \"secret\": \"%s\" }", result.id, result.secret);
- if(buf_len >= 1024) {
- abort();
- }
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- buf_len,
- buf,
- MHD_RESPMEM_MUST_FREE
- );
- ret = MHD_add_response_header(response, "Content-Type", "application/json");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_OK,
- response
- );
- MHD_destroy_response(response);
- return ret;
- } else {
- // Not found
- destroy_post_collector(&request->pp);
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0,
- "",
- MHD_RESPMEM_PERSISTENT
- );
- ret = MHD_add_response_header(response, "Content-Type", "application/json");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_NOT_FOUND,
- response
- );
- MHD_destroy_response(response);
- return ret;
}
- } else if (strcmp(method, "GET") == 0) {
- if (*upload_data_size != 0) return MHD_NO;
-
- if(strcmp(url, "/login") == 0) {
- size_t html_len;
- char *html = html_render_login_page(NULL, &html_len);
- if (!html) return MHD_NO;
- struct MHD_Response *response = MHD_create_response_from_buffer(
- html_len, html, MHD_RESPMEM_MUST_FREE);
- ret = MHD_add_response_header(response, "Content-Type", "text/html");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
- MHD_destroy_response(response);
- return ret;
- } else if(strcmp(url, "/clients") == 0) {
- // Auth check
- if (!is_authenticated(server->app, connection)) {
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- // Fetch clients from app layer
- struct ListClientResult *clients = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16);
- ret = list_clients(server->app, clients);
- if(ret != 0) {
- free(clients);
- return MHD_NO;
- }
-
- // Render HTML
- size_t html_len;
- char *html = html_render_clients_page(request, clients, &html_len);
- free(clients);
-
- if (!html) {
- return MHD_NO;
- }
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- html_len,
- html,
- MHD_RESPMEM_MUST_FREE
- );
- ret = MHD_add_response_header(response, "Content-Type", "text/html");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_OK,
- response
- );
- MHD_destroy_response(response);
- return ret;
- } else if (strncmp(url, "/client/", 8) == 0) {
- // Auth check
- if (!is_authenticated(server->app, connection)) {
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- int64_t client_id;
- if (sscanf(url + 8, "%ld", &client_id) != 1 || client_id <= 0) {
- // Bad Request
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- // Fetch client data from app layer
- struct GetClientOnlyResult client_result;
- ret = get_client(server->app, client_id, &client_result);
- if(ret != 0) {
- return MHD_NO;
- }
-
- if(!client_result.found) {
- // Not Found
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- struct GetBackupsResult *backups_result = malloc(sizeof(struct GetBackupsResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY);
- backups_result->backups_len = MAX_BACKUPS_DISPLAY;
- ret = get_client_backups(server->app, client_id, backups_result);
- if(ret != 0) {
- free(backups_result);
- return MHD_NO;
- }
-
- // Render HTML
- size_t html_len;
- char *html = html_render_client_detail_page(request, &client_result, backups_result, &html_len);
- free(backups_result);
-
- if (!html) {
- // Not Found
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
-
- struct MHD_Response *response = MHD_create_response_from_buffer(
- html_len,
- html,
- MHD_RESPMEM_MUST_FREE
- );
- ret = MHD_add_response_header(response, "Content-Type", "text/html");
- if(ret != MHD_YES) return ret;
- ret = MHD_queue_response(
- connection,
- MHD_HTTP_OK,
- response
- );
- MHD_destroy_response(response);
- return ret;
- } else {
- // Auth check for default page
- if (!is_authenticated(server->app, connection)) {
- struct MHD_Response *response = MHD_create_response_from_buffer(
- 0, "", MHD_RESPMEM_PERSISTENT);
- MHD_add_response_header(response, "Location", "/login");
- ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
- MHD_destroy_response(response);
- return ret;
- }
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
- 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;
- }
+ for (size_t i = 0; i < HANDLER_COUNT; i++) {
+ enum HandlerStatus status = handlers[i](connection, request);
+ if (status == HANDLER_ACCEPTED) return MHD_YES;
+ if (status == HANDLER_ERROR) return MHD_NO;
+ // HANDLER_DECLINE: try next
}
- return MHD_NO;
+ // Should never reach here - handle_not_found always accepts
+ abort();
}
static void request_completed_callback (
diff --git a/src/web.h b/src/web.h
index d957427..a918010 100644
--- a/src/web.h
+++ b/src/web.h
@@ -38,6 +38,21 @@ struct Request {
struct PostCollector pp;
struct MHD_PostProcessor *post_proc; // NULL when using PostCollector
struct FormData form;
+ const char *url;
+ const char *method;
const char *host;
const char *proto;
};
+
+enum HandlerStatus {
+ HANDLER_DECLINE,
+ HANDLER_ACCEPTED,
+ HANDLER_ERROR
+};
+
+struct MHD_Connection;
+
+typedef enum HandlerStatus (*RouteHandler)(
+ struct MHD_Connection *connection,
+ struct Request *request
+);
diff --git a/test/http.c b/test/http.c
index 0d18242..c5a3b2a 100644
--- a/test/http.c
+++ b/test/http.c
@@ -87,59 +87,12 @@ int main(int argc, char **argv) {
char client_secret[CLIENT_SECRET_LEN] = {0};
{
- curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/client");
- assert(curlRes == CURLE_OK);
-
- curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
- assert(curlRes == CURLE_OK);
-
- curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L);
- assert(curlRes == CURLE_OK);
-
- curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory);
- assert(curlRes == CURLE_OK);
-
- struct memory req_body = {
- .body = "{\"name\": \"client1\"}",
- .size = strlen(req_body.body),
- };
- curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body);
- assert(curlRes == CURLE_OK);
-
- curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory);
- assert(curlRes == CURLE_OK);
-
- struct memory body = {0};
- curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
- assert(curlRes == CURLE_OK);
-
- curlRes = curl_easy_perform(curl);
- assert(curlRes == CURLE_OK);
-
- long code;
- curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
- assert(code == 200);
-
- char *ct;
- curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
- assert(curlRes == CURLE_OK);
- assert(ct != NULL);
- assert(strcmp(ct, "application/json") == 0);
-
- assert(body.size >= 0);
- // Parse response to extract secret
- cJSON *rootJson = cJSON_ParseWithLength(body.body, body.size);
- assert(rootJson != NULL);
- cJSON *idJson = cJSON_GetObjectItemCaseSensitive(rootJson, "id");
- assert(cJSON_IsString(idJson));
- assert(strcmp(idJson->valuestring, "1") == 0);
- cJSON *secretJson = cJSON_GetObjectItemCaseSensitive(rootJson, "secret");
- assert(cJSON_IsString(secretJson));
- assert(strlen(secretJson->valuestring) == 32);
- strncpy(client_secret, secretJson->valuestring, CLIENT_SECRET_LEN);
- cJSON_Delete(rootJson);
- free(body.body);
- body = (struct memory){0};
+ struct CreateClientResult result;
+ int ret = create_client(&app, "client1", &result);
+ assert(ret == 0);
+ assert(result.id == 1);
+ assert(strlen(result.secret) == 32);
+ strncpy(client_secret, result.secret, CLIENT_SECRET_LEN);
}
{