Files
ginxsom/src/admin_api.c

816 lines
27 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#ifdef __linux__
#include <sys/statvfs.h>
#else
#include <sys/mount.h>
#endif
#include <unistd.h>
#include "ginxsom.h"
// Database path (consistent with main.c)
#define DB_PATH "db/ginxsom.db"
// Function declarations (moved from admin_api.h)
void handle_admin_api_request(const char* method, const char* uri, const char* validated_pubkey, int is_authenticated);
void handle_stats_api(void);
void handle_config_get_api(void);
void handle_config_put_api(void);
void handle_config_key_put_api(const char* key);
void handle_files_api(void);
void handle_health_api(void);
int authenticate_admin_request(const char* auth_header);
int is_admin_enabled(void);
int verify_admin_pubkey(const char* event_pubkey);
void send_json_response(int status, const char* json_content);
void send_json_error(int status, const char* error, const char* message);
int parse_query_params(const char* query_string, char params[][256], int max_params);
// Forward declarations for local utility functions
static int admin_nip94_get_origin(char* out, size_t out_size);
static void admin_nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size);
static const char* admin_mime_to_extension(const char* mime_type);
// Local utility functions (from main.c but implemented here for admin API)
static int admin_nip94_get_origin(char* out, size_t out_size) {
if (!out || out_size == 0) {
return 0;
}
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
// Default on DB error
strncpy(out, "http://localhost:9001", out_size - 1);
out[out_size - 1] = '\0';
return 1;
}
const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char* value = (const char*)sqlite3_column_text(stmt, 0);
if (value) {
strncpy(out, value, out_size - 1);
out[out_size - 1] = '\0';
sqlite3_finalize(stmt);
sqlite3_close(db);
return 1;
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
// Default fallback
strncpy(out, "http://localhost:9001", out_size - 1);
out[out_size - 1] = '\0';
return 1;
}
static void admin_nip94_build_blob_url(const char* origin, const char* sha256,
const char* mime_type, char* out, size_t out_size) {
if (!origin || !sha256 || !out || out_size == 0) {
return;
}
// Use local admin implementation for extension mapping
const char* extension = admin_mime_to_extension(mime_type);
snprintf(out, out_size, "%s/%s%s", origin, sha256, extension);
}
// Centralized MIME type to file extension mapping (from main.c)
static const char* admin_mime_to_extension(const char* mime_type) {
if (!mime_type) {
return ".bin";
}
if (strstr(mime_type, "image/jpeg")) {
return ".jpg";
} else if (strstr(mime_type, "image/webp")) {
return ".webp";
} else if (strstr(mime_type, "image/png")) {
return ".png";
} else if (strstr(mime_type, "image/gif")) {
return ".gif";
} else if (strstr(mime_type, "video/mp4")) {
return ".mp4";
} else if (strstr(mime_type, "video/webm")) {
return ".webm";
} else if (strstr(mime_type, "audio/mpeg")) {
return ".mp3";
} else if (strstr(mime_type, "audio/ogg")) {
return ".ogg";
} else if (strstr(mime_type, "text/plain")) {
return ".txt";
} else if (strstr(mime_type, "application/pdf")) {
return ".pdf";
} else {
return ".bin";
}
}
// Main API request handler
void handle_admin_api_request(const char* method, const char* uri, const char* validated_pubkey, int is_authenticated) {
const char* path = uri + 4; // Skip "/api"
// Check if admin interface is enabled
if (!is_admin_enabled()) {
send_json_error(503, "admin_disabled", "Admin interface is disabled");
return;
}
// Authentication now handled by centralized validation system
// Health endpoint is exempt from authentication requirement
if (strcmp(path, "/health") != 0) {
if (!is_authenticated || !validated_pubkey) {
send_json_error(401, "admin_auth_required", "Valid admin authentication required");
return;
}
// Verify the authenticated pubkey has admin privileges
if (!verify_admin_pubkey(validated_pubkey)) {
send_json_error(403, "admin_forbidden", "Admin privileges required");
return;
}
}
// Route to appropriate handler
if (strcmp(method, "GET") == 0) {
if (strcmp(path, "/stats") == 0) {
handle_stats_api();
} else if (strcmp(path, "/config") == 0) {
handle_config_get_api();
} else if (strncmp(path, "/files", 6) == 0) {
handle_files_api();
} else if (strcmp(path, "/health") == 0) {
handle_health_api();
} else {
send_json_error(404, "not_found", "API endpoint not found");
}
} else if (strcmp(method, "PUT") == 0) {
if (strcmp(path, "/config") == 0) {
handle_config_put_api();
} else if (strncmp(path, "/config/", 8) == 0) {
const char* key = path + 8; // Skip "/config/"
if (strlen(key) > 0) {
handle_config_key_put_api(key);
} else {
send_json_error(400, "invalid_key", "Configuration key cannot be empty");
}
} else {
send_json_error(405, "method_not_allowed", "Method not allowed");
}
} else {
send_json_error(405, "method_not_allowed", "Method not allowed");
}
}
// Admin authentication functions
int authenticate_admin_request(const char* auth_header) {
if (!auth_header) {
return 0; // No auth header
}
// NOTE: Authentication now handled by centralized validation system in main.c
// This function is kept for compatibility but should receive validation results
// from the centralized system rather than performing duplicate validation
// TODO: Modify to accept validation results from centralized system
// For now, assume validation was successful if we reach here
// and extract pubkey from global context or parameters
return 0; // Temporarily disabled - needs integration with centralized system
}
int verify_admin_pubkey(const char* event_pubkey) {
if (!event_pubkey) {
return 0;
}
sqlite3* db;
sqlite3_stmt* stmt;
int rc, is_admin = 0;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
return 0;
}
const char* sql = "SELECT value FROM config WHERE key = 'admin_pubkey'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char* admin_pubkey = (const char*)sqlite3_column_text(stmt, 0);
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
is_admin = 1;
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return is_admin;
}
int is_admin_enabled(void) {
sqlite3* db;
sqlite3_stmt* stmt;
int rc, enabled = 0;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
return 0; // Default disabled if can't access DB
}
const char* sql = "SELECT value FROM config WHERE key = 'admin_enabled'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char* value = (const char*)sqlite3_column_text(stmt, 0);
enabled = (value && strcmp(value, "true") == 0) ? 1 : 0;
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return enabled;
}
// Individual endpoint handlers
void handle_stats_api(void) {
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
send_json_error(500, "database_error", "Failed to open database");
return;
}
// Create consolidated statistics view if it doesn't exist
const char* create_view =
"CREATE VIEW IF NOT EXISTS storage_stats AS "
"SELECT "
" COUNT(*) as total_blobs, "
" SUM(size) as total_bytes, "
" AVG(size) as avg_blob_size, "
" COUNT(DISTINCT uploader_pubkey) as unique_uploaders, "
" MIN(uploaded_at) as first_upload, "
" MAX(uploaded_at) as last_upload "
"FROM blobs";
rc = sqlite3_exec(db, create_view, NULL, NULL, NULL);
if (rc != SQLITE_OK) {
sqlite3_close(db);
send_json_error(500, "database_error", "Failed to create stats view");
return;
}
// Query storage_stats view
const char* sql = "SELECT total_blobs, total_bytes, avg_blob_size, "
"unique_uploaders, first_upload, last_upload FROM storage_stats";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
sqlite3_close(db);
send_json_error(500, "database_error", "Failed to prepare query");
return;
}
cJSON* response = cJSON_CreateObject();
cJSON* data = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddItemToObject(response, "data", data);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
int total_files = sqlite3_column_int(stmt, 0);
long long total_bytes = sqlite3_column_int64(stmt, 1);
double avg_size = sqlite3_column_double(stmt, 2);
int unique_uploaders = sqlite3_column_int(stmt, 3);
cJSON_AddNumberToObject(data, "total_files", total_files);
cJSON_AddNumberToObject(data, "total_bytes", (double)total_bytes);
cJSON_AddNumberToObject(data, "total_size_mb", (double)total_bytes / (1024 * 1024));
cJSON_AddNumberToObject(data, "unique_uploaders", unique_uploaders);
cJSON_AddNumberToObject(data, "first_upload", sqlite3_column_int64(stmt, 4));
cJSON_AddNumberToObject(data, "last_upload", sqlite3_column_int64(stmt, 5));
cJSON_AddNumberToObject(data, "avg_file_size", avg_size);
// Get file type distribution
sqlite3_stmt* type_stmt;
const char* type_sql = "SELECT type, COUNT(*) FROM blobs GROUP BY type ORDER BY COUNT(*) DESC LIMIT 5";
cJSON* file_types = cJSON_CreateObject();
rc = sqlite3_prepare_v2(db, type_sql, -1, &type_stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(type_stmt) == SQLITE_ROW) {
const char* type_name = (const char*)sqlite3_column_text(type_stmt, 0);
int count = sqlite3_column_int(type_stmt, 1);
cJSON_AddNumberToObject(file_types, type_name ? type_name : "unknown", count);
}
sqlite3_finalize(type_stmt);
}
cJSON_AddItemToObject(data, "file_types", file_types);
} else {
// No data - return zeros
cJSON_AddNumberToObject(data, "total_files", 0);
cJSON_AddNumberToObject(data, "total_bytes", 0);
cJSON_AddNumberToObject(data, "total_size_mb", 0.0);
cJSON_AddNumberToObject(data, "unique_uploaders", 0);
cJSON_AddNumberToObject(data, "avg_file_size", 0);
cJSON_AddItemToObject(data, "file_types", cJSON_CreateObject());
}
sqlite3_finalize(stmt);
sqlite3_close(db);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
}
void handle_config_get_api(void) {
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
send_json_error(500, "database_error", "Failed to open database");
return;
}
cJSON* response = cJSON_CreateObject();
cJSON* data = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddItemToObject(response, "data", data);
// Query all config settings
const char* sql = "SELECT key, value FROM config ORDER BY key";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
if (key && value) {
cJSON_AddStringToObject(data, key, value);
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
}
void handle_config_put_api(void) {
// Read request body
const char* content_length_str = getenv("CONTENT_LENGTH");
if (!content_length_str) {
send_json_error(411, "length_required", "Content-Length header required");
return;
}
long content_length = atol(content_length_str);
if (content_length <= 0 || content_length > 4096) {
send_json_error(400, "invalid_content_length", "Invalid content length");
return;
}
char* json_body = malloc(content_length + 1);
if (!json_body) {
send_json_error(500, "memory_error", "Failed to allocate memory");
return;
}
size_t bytes_read = fread(json_body, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
free(json_body);
send_json_error(400, "incomplete_body", "Failed to read complete request body");
return;
}
json_body[content_length] = '\0';
// Parse JSON
cJSON* config_data = cJSON_Parse(json_body);
if (!config_data) {
free(json_body);
send_json_error(400, "invalid_json", "Invalid JSON in request body");
return;
}
// Update database
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc) {
free(json_body);
cJSON_Delete(config_data);
send_json_error(500, "database_error", "Failed to open database");
return;
}
// Collect updated keys for response
cJSON* updated_keys = cJSON_CreateArray();
// Update each config value
const char* update_sql = "INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, ?)";
cJSON* item = NULL;
cJSON_ArrayForEach(item, config_data) {
if (cJSON_IsString(item) && item->string) {
rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, item->string, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(item), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, time(NULL));
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
cJSON_AddItemToArray(updated_keys, cJSON_CreateString(item->string));
}
sqlite3_finalize(stmt);
}
}
}
free(json_body);
cJSON_Delete(config_data);
sqlite3_close(db);
// Send response
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddStringToObject(response, "message", "Configuration updated successfully");
cJSON_AddItemToObject(response, "updated_keys", updated_keys);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
// Force cache refresh after configuration update
nostr_request_validator_force_cache_refresh();
}
void handle_config_key_put_api(const char* key) {
if (!key || strlen(key) == 0) {
send_json_error(400, "invalid_key", "Configuration key cannot be empty");
return;
}
// Read request body
const char* content_length_str = getenv("CONTENT_LENGTH");
if (!content_length_str) {
send_json_error(411, "length_required", "Content-Length header required");
return;
}
long content_length = atol(content_length_str);
if (content_length <= 0 || content_length > 4096) {
send_json_error(400, "invalid_content_length", "Invalid content length");
return;
}
char* json_body = malloc(content_length + 1);
if (!json_body) {
send_json_error(500, "memory_error", "Failed to allocate memory");
return;
}
size_t bytes_read = fread(json_body, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
free(json_body);
send_json_error(400, "incomplete_body", "Failed to read complete request body");
return;
}
json_body[content_length] = '\0';
// Parse JSON - expect {"value": "..."}
cJSON* request_data = cJSON_Parse(json_body);
if (!request_data) {
free(json_body);
send_json_error(400, "invalid_json", "Invalid JSON in request body");
return;
}
cJSON* value_item = cJSON_GetObjectItem(request_data, "value");
if (!cJSON_IsString(value_item)) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(400, "missing_value", "Request must contain 'value' field");
return;
}
const char* value = cJSON_GetStringValue(value_item);
if (!value) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(400, "invalid_value", "Value must be a string");
return;
}
// Make a safe copy of the value string BEFORE deleting cJSON object
char safe_value[256];
strncpy(safe_value, value, sizeof(safe_value) - 1);
safe_value[sizeof(safe_value) - 1] = '\0';
// Update database
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(500, "database_error", "Failed to open database");
return;
}
// Update or insert the config value
const char* update_sql = "INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, ?)";
rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
free(json_body);
cJSON_Delete(request_data);
sqlite3_close(db);
send_json_error(500, "database_error", "Failed to prepare update statement");
return;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, safe_value, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, time(NULL));
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
free(json_body);
cJSON_Delete(request_data);
if (rc != SQLITE_DONE) {
send_json_error(500, "database_error", "Failed to update configuration");
return;
}
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddStringToObject(response, "message", "Configuration updated successfully");
cJSON_AddStringToObject(response, "key", key);
cJSON_AddStringToObject(response, "value", safe_value);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
// Force cache refresh after configuration update
nostr_request_validator_force_cache_refresh();
}
void handle_files_api(void) {
// Parse query parameters for pagination
const char* query_string = getenv("QUERY_STRING");
int limit = 50;
int offset = 0;
if (query_string) {
char params[10][256];
int param_count = parse_query_params(query_string, params, 10);
for (int i = 0; i < param_count; i++) {
char* key = params[i];
char* value = strchr(key, '=');
if (value) {
*value++ = '\0';
if (strcmp(key, "limit") == 0) {
limit = atoi(value);
if (limit <= 0 || limit > 200) limit = 50;
} else if (strcmp(key, "offset") == 0) {
offset = atoi(value);
if (offset < 0) offset = 0;
}
}
}
}
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
send_json_error(500, "database_error", "Failed to open database");
return;
}
// Query recent files with pagination
const char* sql = "SELECT sha256, size, type, uploaded_at, uploader_pubkey, filename "
"FROM blobs ORDER BY uploaded_at DESC LIMIT ? OFFSET ?";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
sqlite3_close(db);
send_json_error(500, "database_error", "Failed to prepare query");
return;
}
sqlite3_bind_int(stmt, 1, limit);
sqlite3_bind_int(stmt, 2, offset);
cJSON* response = cJSON_CreateObject();
cJSON* data = cJSON_CreateObject();
cJSON* files_array = cJSON_CreateArray();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddItemToObject(response, "data", data);
cJSON_AddItemToObject(data, "files", files_array);
cJSON_AddNumberToObject(data, "limit", limit);
cJSON_AddNumberToObject(data, "offset", offset);
int total_count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
total_count++;
cJSON* file_obj = cJSON_CreateObject();
cJSON_AddItemToArray(files_array, file_obj);
const char* sha256 = (const char*)sqlite3_column_text(stmt, 0);
const char* type = (const char*)sqlite3_column_text(stmt, 2);
const char* filename = (const char*)sqlite3_column_text(stmt, 5);
cJSON_AddStringToObject(file_obj, "sha256", sha256 ? sha256 : "");
cJSON_AddNumberToObject(file_obj, "size", sqlite3_column_int64(stmt, 1));
cJSON_AddStringToObject(file_obj, "type", type ? type : "");
cJSON_AddNumberToObject(file_obj, "uploaded_at", sqlite3_column_int64(stmt, 3));
const char* uploader = (const char*)sqlite3_column_text(stmt, 4);
cJSON_AddStringToObject(file_obj, "uploader_pubkey",
uploader ? uploader : "");
cJSON_AddStringToObject(file_obj, "filename",
filename ? filename : "");
// Build URL for file
char url[1024];
if (type && sha256) {
// Use local admin implementation for URL building
char origin_url[256];
admin_nip94_get_origin(origin_url, sizeof(origin_url));
admin_nip94_build_blob_url(origin_url, sha256, type, url, sizeof(url));
cJSON_AddStringToObject(file_obj, "url", url);
}
}
// Get total count for pagination info
const char* count_sql = "SELECT COUNT(*) FROM blobs";
sqlite3_stmt* count_stmt;
rc = sqlite3_prepare_v2(db, count_sql, -1, &count_stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(count_stmt);
if (rc == SQLITE_ROW) {
int total = sqlite3_column_int(count_stmt, 0);
cJSON_AddNumberToObject(data, "total", total);
}
sqlite3_finalize(count_stmt);
}
sqlite3_finalize(stmt);
sqlite3_close(db);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
}
void handle_health_api(void) {
cJSON* response = cJSON_CreateObject();
cJSON* data = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddItemToObject(response, "data", data);
// Check database connection
sqlite3* db;
int rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc == SQLITE_OK) {
cJSON_AddStringToObject(data, "database", "connected");
sqlite3_close(db);
} else {
cJSON_AddStringToObject(data, "database", "disconnected");
}
// Check blob directory
struct stat st;
if (stat("blobs", &st) == 0 && S_ISDIR(st.st_mode)) {
cJSON_AddStringToObject(data, "blob_directory", "accessible");
} else {
cJSON_AddStringToObject(data, "blob_directory", "inaccessible");
}
// Get disk usage
cJSON* disk_usage = cJSON_CreateObject();
struct statvfs vfs;
if (statvfs(".", &vfs) == 0) {
unsigned long long total_bytes = (unsigned long long)vfs.f_blocks * vfs.f_frsize;
unsigned long long free_bytes = (unsigned long long)vfs.f_bavail * vfs.f_frsize;
unsigned long long used_bytes = total_bytes - free_bytes;
double usage_percent = (double)used_bytes / (double)total_bytes * 100.0;
cJSON_AddNumberToObject(disk_usage, "total_bytes", (double)total_bytes);
cJSON_AddNumberToObject(disk_usage, "used_bytes", (double)used_bytes);
cJSON_AddNumberToObject(disk_usage, "available_bytes", (double)free_bytes);
cJSON_AddNumberToObject(disk_usage, "usage_percent", usage_percent);
}
cJSON_AddItemToObject(data, "disk_usage", disk_usage);
// Add server info
cJSON_AddNumberToObject(data, "server_time", (double)time(NULL));
cJSON_AddNumberToObject(data, "uptime", 0); // Would need to track process start time
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
}
// Utility functions
void send_json_response(int status, const char* json_content) {
printf("Status: %d OK\r\n", status);
printf("Content-Type: application/json\r\n");
printf("Cache-Control: no-cache\r\n");
printf("\r\n");
printf("%s\n", json_content);
}
void send_json_error(int status, const char* error, const char* message) {
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "error");
cJSON_AddStringToObject(response, "error", error);
cJSON_AddStringToObject(response, "message", message);
char* response_str = cJSON_PrintUnformatted(response);
printf("Status: %d %s\r\n",
status,
status == 400 ? "Bad Request" :
status == 401 ? "Unauthorized" :
status == 403 ? "Forbidden" :
status == 404 ? "Not Found" :
status == 500 ? "Internal Server Error" :
status == 503 ? "Service Unavailable" :
"Error");
printf("Content-Type: application/json\r\n");
printf("Cache-Control: no-cache\r\n");
printf("\r\n");
printf("%s\n", response_str);
free(response_str);
cJSON_Delete(response);
}
int parse_query_params(const char* query_string, char params[][256], int max_params) {
if (!query_string || !params) return 0;
size_t query_len = strlen(query_string);
char* query_copy = malloc(query_len + 1);
if (!query_copy) return 0;
memcpy(query_copy, query_string, query_len + 1);
int count = 0;
char* token = strtok(query_copy, "&");
while (token && count < max_params) {
if (strlen(token) >= sizeof(params[0])) {
token[sizeof(params[0]) - 1] = '\0';
}
strcpy(params[count], token);
count++;
token = strtok(NULL, "&");
}
free(query_copy);
return count;
}