v0.1.4 - Make response at root JSON

This commit is contained in:
Your Name
2025-11-11 07:08:27 -04:00
parent 30e4408b28
commit fe2495f897
20 changed files with 2095 additions and 328 deletions

View File

@@ -426,9 +426,9 @@ void handle_mirror_request(void) {
// Determine file extension from Content-Type using centralized mapping
const char* extension = mime_to_extension(content_type_final);
// Save file to blobs directory
// Save file to storage directory using global g_storage_dir variable
char filepath[512];
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
snprintf(filepath, sizeof(filepath), "%s/%s%s", g_storage_dir, sha256_hex, extension);
FILE* outfile = fopen(filepath, "wb");
if (!outfile) {

View File

@@ -24,7 +24,7 @@ int nip94_is_enabled(void) {
return 1; // Default enabled on DB error
}
const char* sql = "SELECT value FROM server_config WHERE key = 'nip94_enabled'";
const char* sql = "SELECT value FROM config WHERE key = 'nip94_enabled'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
@@ -45,49 +45,52 @@ int nip94_get_origin(char* out, size_t out_size) {
return 0;
}
// Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests)
const char* https_env = getenv("HTTPS");
if (https_env && strcmp(https_env, "on") == 0) {
// HTTPS request - use HTTPS origin
strncpy(out, "https://localhost:9443", out_size - 1);
out[out_size - 1] = '\0';
return 1;
}
// HTTP request - check database config first, then use HTTP origin
// Check database config first for custom origin
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
// Default on DB error - use HTTP
strncpy(out, "http://localhost:9001", out_size - 1);
out[out_size - 1] = '\0';
if (rc == SQLITE_OK) {
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);
}
// Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests)
const char* https_env = getenv("HTTPS");
const char* server_name = getenv("SERVER_NAME");
// Use production domain if SERVER_NAME is set and not localhost
if (server_name && strcmp(server_name, "localhost") != 0) {
if (https_env && strcmp(https_env, "on") == 0) {
snprintf(out, out_size, "https://%s", server_name);
} else {
snprintf(out, out_size, "http://%s", server_name);
}
return 1;
}
const char* sql = "SELECT value FROM server_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);
// Fallback to localhost for development
if (https_env && strcmp(https_env, "on") == 0) {
strncpy(out, "https://localhost:9443", out_size - 1);
} else {
strncpy(out, "http://localhost:9001", out_size - 1);
}
sqlite3_close(db);
// Default fallback - HTTP
strncpy(out, "http://localhost:9001", out_size - 1);
out[out_size - 1] = '\0';
return 1;
}

View File

@@ -30,6 +30,10 @@ extern sqlite3* db;
int init_database(void);
void close_database(void);
// Global configuration variables (defined in main.c)
extern char g_db_path[4096];
extern char g_storage_dir[4096];
// SHA-256 extraction and validation
const char* extract_sha256_from_uri(const char* uri);

View File

@@ -7,6 +7,7 @@
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include <getopt.h>
#include <curl/curl.h>
#include <fcgi_stdio.h>
#include <sqlite3.h>
@@ -22,11 +23,15 @@
// Debug macros removed
#define MAX_SHA256_LEN 65
#define MAX_PATH_LEN 512
#define MAX_PATH_LEN 4096
#define MAX_MIME_LEN 128
// Database path
#define DB_PATH "db/ginxsom.db"
// Configuration variables - can be overridden via command line
char g_db_path[MAX_PATH_LEN] = "db/ginxsom.db";
char g_storage_dir[MAX_PATH_LEN] = ".";
// Use global configuration variables
#define DB_PATH g_db_path
// Configuration system implementation
@@ -35,22 +40,6 @@
#include <sys/stat.h>
#include <unistd.h>
// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING =====
// Server configuration structure
/*
typedef struct {
char admin_pubkey[256];
char admin_enabled[8];
int config_loaded;
} server_config_t;
// Global configuration instance
static server_config_t g_server_config = {0};
// Global server private key (stored in memory only for security)
static char server_private_key[128] = {0};
*/
// ===== END UNUSED CODE =====
// Function to get XDG config directory
const char *get_config_dir(char *buffer, size_t buffer_size) {
@@ -70,240 +59,6 @@ const char *get_config_dir(char *buffer, size_t buffer_size) {
return ".config/ginxsom";
}
/*
// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING =====
// Load server configuration from database or create defaults
int initialize_server_config(void) {
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
int rc;
memset(&g_server_config, 0, sizeof(g_server_config));
// Open database
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "CONFIG: Could not open database for config: %s\n",
sqlite3_errmsg(db));
// Config database doesn't exist - leave config uninitialized
g_server_config.config_loaded = 0;
return 0;
}
// Load admin_pubkey
const char *sql = "SELECT value FROM config WHERE key = ?";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *value = (const char *)sqlite3_column_text(stmt, 0);
if (value) {
strncpy(g_server_config.admin_pubkey, value,
sizeof(g_server_config.admin_pubkey) - 1);
}
}
sqlite3_finalize(stmt);
}
// Load admin_enabled
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *value = (const char *)sqlite3_column_text(stmt, 0);
if (value && strcmp(value, "true") == 0) {
strcpy(g_server_config.admin_enabled, "true");
} else {
strcpy(g_server_config.admin_enabled, "false");
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
g_server_config.config_loaded = 1;
fprintf(stderr, "CONFIG: Server configuration loaded\n");
return 1;
}
// ===== END UNUSED CODE =====
*/
/*
// File-based configuration system
// Config file path resolution
int get_config_file_path(char *path, size_t path_size) {
const char *home = getenv("HOME");
const char *xdg_config = getenv("XDG_CONFIG_HOME");
if (xdg_config) {
snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json",
xdg_config);
} else if (home) {
snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json",
home);
} else {
return 0;
}
return 1;
}
*/
/*
// Load and validate config event
int load_server_config(const char *config_path) {
FILE *file = fopen(config_path, "r");
if (!file) {
return 0; // Config file doesn't exist
}
// Read entire file
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
char *json_data = malloc(file_size + 1);
if (!json_data) {
fclose(file);
return 0;
}
fread(json_data, 1, file_size, file);
json_data[file_size] = '\0';
fclose(file);
// Parse and validate JSON event
cJSON *event = cJSON_Parse(json_data);
free(json_data);
if (!event) {
fprintf(stderr, "Invalid JSON in config file\n");
return 0;
}
// Validate event structure and signature
if (nostr_validate_event(event) != NOSTR_SUCCESS) {
fprintf(stderr, "Invalid or corrupted config event\n");
cJSON_Delete(event);
return 0;
}
// Extract configuration and apply to server
int result = apply_config_from_event(event);
cJSON_Delete(event);
return result;
}
*/
/*
// Extract config from validated event and apply to server
int apply_config_from_event(cJSON *event) {
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
// Open database for config storage
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc) {
fprintf(stderr, "Failed to open database for config\n");
return 0;
}
// Extract admin pubkey from event
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
sqlite3_close(db);
return 0;
}
const char *admin_pubkey = cJSON_GetStringValue(pubkey_json);
// Store admin pubkey in database
const char *insert_sql = "INSERT OR REPLACE INTO config (key, value, "
"description) VALUES (?, ?, ?)";
rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, "Admin public key from config event", -1,
SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
// Extract server private key and store securely (in memory only)
cJSON *tags = cJSON_GetObjectItem(event, "tags");
if (tags && cJSON_IsArray(tags)) {
cJSON *tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag))
continue;
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
if (!tag_name || !cJSON_IsString(tag_name) || !tag_value ||
!cJSON_IsString(tag_value))
continue;
const char *key = cJSON_GetStringValue(tag_name);
const char *value = cJSON_GetStringValue(tag_value);
if (strcmp(key, "server_privkey") == 0) {
// Store server private key in global variable (memory only)
// strncpy(server_private_key, value, sizeof(server_private_key) - 1);
// server_private_key[sizeof(server_private_key) - 1] = '\0';
} else {
// Store other config values in database
rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
}
}
}
sqlite3_close(db);
return 1;
}
*/
/*
// Interactive setup runner
int run_interactive_setup(const char *config_path) {
printf("\n=== Ginxsom First-Time Setup Required ===\n");
printf("No configuration found at: %s\n\n", config_path);
printf("Options:\n");
printf("1. Run interactive setup wizard\n");
printf("2. Exit and create config manually\n");
printf("Choice (1/2): ");
char choice[10];
if (!fgets(choice, sizeof(choice), stdin)) {
return 1;
}
if (choice[0] == '1') {
// Run setup script
char script_path[512];
snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"",
config_path);
return system(script_path);
} else {
printf("\nManual setup instructions:\n");
printf("1. Run: ./scripts/generate_config.sh\n");
printf("2. Place signed config at: %s\n", config_path);
printf("3. Restart ginxsom\n");
return 1;
}
}
*/
// Function declarations
void handle_options_request(void);
@@ -434,7 +189,23 @@ int file_exists_with_type(const char *sha256, const char *mime_type) {
char filepath[MAX_PATH_LEN];
const char *extension = mime_to_extension(mime_type);
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension);
// Construct path safely
size_t dir_len = strlen(g_storage_dir);
size_t sha_len = strlen(sha256);
size_t ext_len = strlen(extension);
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
return 0;
}
// Build path manually to avoid compiler warnings
memcpy(filepath, g_storage_dir, dir_len);
filepath[dir_len] = '/';
memcpy(filepath + dir_len + 1, sha256, sha_len);
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
filepath[total_len - 1] = '\0';
struct stat st;
int result = stat(filepath, &st);
@@ -524,18 +295,6 @@ const char *extract_sha256_from_uri(const char *uri) {
return sha256_buffer;
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD 02 - Upload & Authentication
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// AUTHENTICATION RULES SYSTEM (4.1.2)
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
@@ -911,7 +670,23 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
}
char filepath[MAX_PATH_LEN];
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension);
// Construct path safely
size_t dir_len = strlen(g_storage_dir);
size_t sha_len = strlen(sha256);
size_t ext_len = strlen(extension);
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
// Continue anyway - unlink will fail gracefully
} else {
// Build path manually to avoid compiler warnings
memcpy(filepath, g_storage_dir, dir_len);
filepath[dir_len] = '/';
memcpy(filepath + dir_len + 1, sha256, sha_len);
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
filepath[total_len - 1] = '\0';
}
// Delete the physical file
if (unlink(filepath) != 0) {
@@ -1029,9 +804,29 @@ void handle_upload_request(void) {
// Determine file extension from Content-Type using centralized mapping
const char *extension = mime_to_extension(content_type);
// Save file to blobs directory with SHA-256 + extension
// Save file to storage directory with SHA-256 + extension
char filepath[MAX_PATH_LEN];
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
// Construct path safely
size_t dir_len = strlen(g_storage_dir);
size_t sha_len = strlen(sha256_hex);
size_t ext_len = strlen(extension);
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("File path too long\n");
return;
}
// Build path manually to avoid compiler warnings
memcpy(filepath, g_storage_dir, dir_len);
filepath[dir_len] = '/';
memcpy(filepath + dir_len + 1, sha256_hex, sha_len);
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
filepath[total_len - 1] = '\0';
FILE *outfile = fopen(filepath, "wb");
if (!outfile) {
@@ -1280,9 +1075,29 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// Determine file extension from Content-Type using centralized mapping
const char *extension = mime_to_extension(content_type);
// Save file to blobs directory with SHA-256 + extension
// Save file to storage directory with SHA-256 + extension
char filepath[MAX_PATH_LEN];
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
// Construct path safely
size_t dir_len = strlen(g_storage_dir);
size_t sha_len = strlen(sha256_hex);
size_t ext_len = strlen(extension);
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("File path too long\n");
return;
}
// Build path manually to avoid compiler warnings
memcpy(filepath, g_storage_dir, dir_len);
filepath[dir_len] = '/';
memcpy(filepath + dir_len + 1, sha256_hex, sha_len);
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
filepath[total_len - 1] = '\0';
FILE *outfile = fopen(filepath, "wb");
if (!outfile) {
@@ -1480,7 +1295,31 @@ void handle_auth_challenge_request(void) {
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
int main(void) {
int main(int argc, char *argv[]) {
// Parse command line arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--db-path") == 0 && i + 1 < argc) {
strncpy(g_db_path, argv[i + 1], sizeof(g_db_path) - 1);
i++; // Skip next argument
} else if (strcmp(argv[i], "--storage-dir") == 0 && i + 1 < argc) {
strncpy(g_storage_dir, argv[i + 1], sizeof(g_storage_dir) - 1);
i++; // Skip next argument
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" --db-path PATH Database file path (default: db/ginxsom.db)\n");
printf(" --storage-dir DIR Storage directory for files (default: blobs)\n");
printf(" --help, -h Show this help message\n");
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
fprintf(stderr, "Use --help for usage information\n");
return 1;
}
}
fprintf(stderr, "STARTUP: Using database path: %s\n", g_db_path);
fprintf(stderr, "STARTUP: Using storage directory: %s\n", g_storage_dir);
// Initialize server configuration and identity
// Try file-based config first, then fall back to database config