#include "web.h" #include "mytime.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGE "libmicrohttpd demo"\ "libmicrohttpd demo" static void sqliteError(void *pArg, int iErrCode, const char *zMsg) { // @CLEANUP: We should stuff these somewhere, but for now just print it fprintf(stderr, "(%d) %s\n", iErrCode, zMsg); } static int generate_secret(char *out) { unsigned char bytes[16]; int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; ssize_t n = read(fd, bytes, 16); close(fd); if (n != 16) return -1; static const char hex[] = "0123456789abcdef"; for (int i = 0; i < 16; i++) { out[i*2] = hex[(bytes[i] >> 4) & 0x0f]; out[i*2+1] = hex[bytes[i] & 0x0f]; } out[32] = '\0'; return 0; } void prepare_database(struct App *app) { // @CLEANUP: Move this to main? doesn't belong here at least sqlite3_config(SQLITE_CONFIG_LOG, sqliteError, NULL); sqlite3_config(SQLITE_CONFIG_URI, 1); int err; printf("Running migrations against %s\n", app->dbname); sqlite3 *conn; if((err = sqlite3_open(app->dbname, &conn)) != SQLITE_OK) { fprintf(stderr, "(%d) %s\n", err, sqlite3_errmsg(conn)); abort(); } sqlite3_db_config(conn, SQLITE_DBCONFIG_ENABLE_FKEY, 1, NULL); if(sqlite3_exec(conn, "CREATE TABLE IF NOT EXISTS migration (id INTEGER PRIMARY KEY NOT NULL)", NULL, NULL, NULL) != SQLITE_OK) { abort(); } sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "SELECT MAX(id) FROM migration", -1, &stmt, NULL) != SQLITE_OK) { abort(); } uint64_t maxVersion = 0; while((err = sqlite3_step(stmt)) == SQLITE_ROW) { maxVersion = sqlite3_column_int64(stmt, 0); } if(err != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } if(maxVersion < 1) { char *prog = "BEGIN TRANSACTION;" "CREATE TABLE backups (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "client TEXT NOT NULL, " "status TEXT NOT NULL, " "started TEXT NOT NULL, " "completed TEXT " ") STRICT"; if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { abort(); } sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, 1) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { abort(); } } if(maxVersion < 2) { char *prog = "BEGIN TRANSACTION;" "CREATE TABLE clients (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "name TEXT NOT NULL " ") STRICT;" "INSERT INTO clients(name) " "SELECT b.client " "FROM backups b " "GROUP BY b.client;" "CREATE TABLE backups_next (" "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " "client INTEGER NOT NULL, " "status TEXT NOT NULL, " "started TEXT NOT NULL, " "completed TEXT ," "FOREIGN KEY(client) REFERENCES clients(id)" ") STRICT;" "INSERT INTO backups_next(id, client, status, started, completed) " "SELECT b.id, c.id, b.status, b.started, b.completed " "FROM backups b " "JOIN clients c ON b.client = c.name;" "DROP TABLE backups;" "ALTER TABLE backups_next RENAME TO backups;"; if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { abort(); } sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, 2) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { abort(); } } if(maxVersion < 3) { char *prog = "BEGIN TRANSACTION;" "CREATE UNIQUE INDEX clients_name ON clients(name)"; if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { abort(); } sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, 3) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { abort(); } } if(maxVersion < 4) { char *prog = "BEGIN TRANSACTION;" "ALTER TABLE clients ADD COLUMN secret TEXT NOT NULL DEFAULT ''"; if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { abort(); } sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, 4) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { abort(); } } } enum ReportEvent { REV_BEGIN, REV_FINISH, }; // @CLEANUP: This should live somewhere else, but I don't have a spot for it yet. // Returns 0 on success, -1 on error, -2 on secret mismatch int submit_report(struct App *app, char *client, char *secret, enum ReportEvent event) { sqlite3 *conn; int err; if((err = sqlite3_open(app->dbname, &conn)) != SQLITE_OK) { abort(); } struct timespec now = get_current_time(); int64_t clientId; { sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "SELECT id, secret FROM clients WHERE name = ?1", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 1, client, -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_ROW) { abort(); } if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { abort(); } clientId = sqlite3_column_int64(stmt, 0); const char *stored_secret = (const char *)sqlite3_column_text(stmt, 1); if(stored_secret == NULL || strcmp(stored_secret, secret) != 0) { sqlite3_finalize(stmt); sqlite3_close(conn); return -2; } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } switch(event) { case REV_BEGIN: { sqlite3_stmt *stmt; // Mark any existing RUNNING backup as INTERRUPTED if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = 'INTERRUPTED', completed = datetime(?1, 'unixepoch', 'subsec') WHERE client = ?2 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_double(stmt, 1, TIME_AS_FLOAT(now)) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 2, clientId) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } stmt = NULL; // Insert the new backup if(sqlite3_prepare_v2(conn, "INSERT INTO backups (client, status, started) VALUES (?1, ?2, datetime(?3, 'unixepoch', 'subsec'))", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 2, "RUNNING", -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_bind_double(stmt, 3, TIME_AS_FLOAT(now)) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } break; } case REV_FINISH: { uint64_t backupId; sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "SELECT id FROM backups WHERE client = ?1 AND status = 'RUNNING'", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, clientId) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_ROW) { abort(); } if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { abort(); } backupId = sqlite3_column_int64(stmt, 0); if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } stmt = NULL; if(sqlite3_prepare_v2(conn, "UPDATE backups SET status = ?1, completed = datetime(?2, 'unixepoch', 'subsec') WHERE id = ?3", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 1, "FINISHED", -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_bind_double(stmt, 2, TIME_AS_FLOAT(now)) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 3, backupId) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } break; } } if(sqlite3_close(conn) != SQLITE_OK) { abort(); } return 0; } int create_client(struct Request *req, char *name, struct CreateClientResult *result) { sqlite3 *conn; int err; if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { abort(); } if(generate_secret(result->secret) != 0) { abort(); } { sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "INSERT INTO clients (name, secret) VALUES (?1, ?2)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 2, result->secret, -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } result->id = sqlite3_last_insert_rowid(conn); if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } if(sqlite3_close(conn) != SQLITE_OK) { abort(); } return 0; } int delete_client(struct Request *req, int64_t client_id) { sqlite3 *conn; if(sqlite3_open(req->server->app->dbname, &conn) != SQLITE_OK) { abort(); } 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; } 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; if(sqlite3_prepare_v2(conn, "SELECT c.id, c.name, c.secret FROM clients c WHERE c.id > ?1 ORDER BY c.id ASC LIMIT ?2", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, -1) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 2, 16) != SQLITE_OK) { abort(); } int ret; int index = 0; while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { assert(index < 16); if(sqlite3_column_type(stmt, 0) == SQLITE_NULL) { abort(); } int64_t clientId = sqlite3_column_int64(stmt, 0); const uint8_t *name = sqlite3_column_text(stmt, 1); const uint8_t *secret = sqlite3_column_text(stmt, 2); result->clients[index].id = clientId; strncpy(result->clients[index].name, (char*)name, CLIENT_NAME_MAX); strncpy(result->clients[index].secret, (char*)secret, CLIENT_SECRET_LEN); index++; } if(ret != SQLITE_DONE) { abort(); } result->clients_len = index; if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } if(sqlite3_close(conn) != SQLITE_OK) { abort(); } return 0; } int get_client_with_backups(struct Request *req, int64_t client_id, struct GetClientResult *result) { sqlite3 *conn; int err; if((err = sqlite3_open(req->server->app->dbname, &conn)) != SQLITE_OK) { abort(); } result->found = 0; // Query 1: Get client info { sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "SELECT id, name, secret FROM clients WHERE id = ?1", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { abort(); } int ret = sqlite3_step(stmt); if(ret == SQLITE_ROW) { result->found = 1; result->id = sqlite3_column_int64(stmt, 0); const uint8_t *name = sqlite3_column_text(stmt, 1); const uint8_t *secret = sqlite3_column_text(stmt, 2); strncpy(result->name, (char*)name, CLIENT_NAME_MAX); strncpy(result->secret, (char*)secret, CLIENT_SECRET_LEN); } else if(ret != SQLITE_DONE) { abort(); } if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } if(!result->found) { if(sqlite3_close(conn) != SQLITE_OK) { abort(); } return 0; } // Query 2: Get backups for this client { sqlite3_stmt *stmt; if(sqlite3_prepare_v2(conn, "SELECT id, status, started, completed FROM backups WHERE client = ?1 ORDER BY started DESC LIMIT 50", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, client_id) != SQLITE_OK) { abort(); } int ret; int index = 0; while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { if(index >= MAX_BACKUPS_DISPLAY) break; result->backups[index].id = sqlite3_column_int64(stmt, 0); const uint8_t *status = sqlite3_column_text(stmt, 1); const uint8_t *started = sqlite3_column_text(stmt, 2); const uint8_t *completed = sqlite3_column_text(stmt, 3); strncpy(result->backups[index].status, status ? (char*)status : "", BACKUP_STATUS_MAX); strncpy(result->backups[index].started, started ? (char*)started : "", BACKUP_DATETIME_MAX); strncpy(result->backups[index].completed, completed ? (char*)completed : "", BACKUP_DATETIME_MAX); index++; } if(ret != SQLITE_DONE && ret != SQLITE_ROW) { abort(); } result->backups_len = index; if(sqlite3_finalize(stmt) != SQLITE_OK) { abort(); } } if(sqlite3_close(conn) != SQLITE_OK) { abort(); } return 0; } 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; } char *buf = NULL; size_t buf_len = 0; FILE *f = open_memstream(&buf, &buf_len); if (!f) { free(result); return NULL; } 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" " \n", result->clients[i].id, result->clients[i].name, result->clients[i].secret, result->clients[i].id ); } fprintf(f, "
NameSecretActions
%s%s\n" "
\n" " \n" " \n" "
\n" "
\n" "

Add New Client

\n" "
\n" " \n" " \n" "
\n" "\n" "\n"); fclose(f); *out_len = buf_len; free(result); return buf; } static char *render_client_detail_page(struct Request *request, int64_t client_id, size_t *out_len) { struct GetClientResult *result = malloc(sizeof(struct GetClientResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY); int ret = get_client_with_backups(request, client_id, result); if(ret != 0) { free(result); return NULL; } if(!result->found) { free(result); return NULL; } char *buf = NULL; size_t buf_len = 0; FILE *f = open_memstream(&buf, &buf_len); if (!f) { free(result); return NULL; } fprintf(f, "\n" "\n" "\n" " Client: %s\n" "\n" "\n" "

Clients > %s

\n" "

Client: %s

\n" "

Details

\n" " \n" " \n" " \n" " \n" "
ID%ld
Name%s
Secret%s
\n", result->name, result->name, result->name, result->id, result->name, result->secret ); fprintf(f, "

Submit Report

\n" "

Use this curl command to submit a backup report:

\n" "
curl -X POST http://%s/api/report \\\n"
		"  -H \"Content-Type: application/json\" \\\n"
		"  -d '{\"client\": \"%s\", \"secret\": \"%s\", \"event\": \"begin_backup\"}'
\n" "

Replace begin_backup with end_backup to mark completion.

\n", request->host ? request->host : "localhost", result->name, result->secret ); fprintf(f, "

Backup History

\n"); if(result->backups_len == 0) { fprintf(f, "

No backups recorded.

\n"); } else { fprintf(f, " \n" " \n"); for (int i = 0; i < result->backups_len; i++) { fprintf(f, " \n" " \n" " \n" " \n" " \n" " \n", result->backups[i].id, result->backups[i].status, result->backups[i].started, result->backups[i].completed[0] ? result->backups[i].completed : "-" ); } fprintf(f, "
IDStatusStartedCompleted
%ld%s%s%s
\n"); } fprintf(f, "\n" "\n"); fclose(f); *out_len = buf_len; free(result); return buf; } static enum MHD_Result create_post_collector(struct PostCollector *c) { c->data = malloc(512); c->size = 0; c->capacity = 512; return MHD_YES; } static enum MHD_Result collect_post(struct PostCollector *c, const char *data, size_t *data_len) { size_t newSize = c->size + *data_len; if(newSize > c->capacity) { while(newSize > c->capacity) c->capacity *= 2; c->data = realloc(c->data, c->capacity); } memcpy(c->data + c->size, data, *data_len); c->size = newSize; return MHD_YES; } static enum MHD_Result destroy_post_collector(struct PostCollector *c) { if(c->capacity != 0) { free(c->data); c->capacity = 0; } 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, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, 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)); if(request == NULL) { return MHD_NO; } *ptr = request; request->server = server; request->host = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Host"); if(strcmp(method, "POST") == 0) { 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) { 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, "/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); 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(); } enum ReportEvent event; if(strcmp(eventJson->valuestring, "begin_backup") == 0) { event = REV_BEGIN; } else if(strcmp(eventJson->valuestring, "end_backup") == 0) { event = REV_FINISH; } else { abort(); } ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, event); 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(request, 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, "/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( 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) { 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; } size_t html_len; char *html = render_client_detail_page(request, client_id, &html_len); 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 { 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; } } return MHD_NO; } static void request_completed_callback ( void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe ) { struct Request *request = *con_cls; if (request->post_proc != NULL) { MHD_destroy_post_processor(request->post_proc); } destroy_post_collector(&request->pp); free(request); } static struct MHD_Daemon *d = NULL; int web_begin(struct Server *server, int port) { assert(d == NULL); server->page = PAGE; d = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK, port, NULL, NULL, &handler, server, MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL, MHD_OPTION_END ); if (d == NULL) return 1; return 0; } int web_join(struct Server *server) { assert(d != NULL); MHD_stop_daemon(d); d = NULL; return 0; }