diff --git a/restart-all.sh b/Trash/restart-all.sh similarity index 100% rename from restart-all.sh rename to Trash/restart-all.sh diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi index 1771242..ad0d511 100755 Binary files a/build/ginxsom-fcgi and b/build/ginxsom-fcgi differ diff --git a/build/main.o b/build/main.o index 227e54b..f9db3ee 100644 Binary files a/build/main.o and b/build/main.o differ diff --git a/build/request_validator.o b/build/request_validator.o index 6dbfba8..3640729 100644 Binary files a/build/request_validator.o and b/build/request_validator.o differ diff --git a/config/ginxsom-local.conf b/config/ginxsom-local.conf index 30c64a7..814ff19 100644 --- a/config/ginxsom-local.conf +++ b/config/ginxsom-local.conf @@ -26,51 +26,122 @@ server { add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-SHA-256, X-Content-Type, X-Content-Length" always; add_header Access-Control-Max-Age 86400 always; - # Handle preflight requests - location / { - if ($request_method = 'OPTIONS') { - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods "GET, HEAD, PUT, DELETE, OPTIONS"; - add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-SHA-256, X-Content-Type, X-Content-Length"; - add_header Access-Control-Max-Age 86400; - add_header Content-Length 0; - add_header Content-Type text/plain; - return 204; - } - - # Pass all requests to FastCGI - include fastcgi_params; - fastcgi_pass unix:/tmp/ginxsom.sock; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + # Root directory for blob storage + root /home/teknari/Storage/Blossom; + + # Maximum upload size + client_max_body_size 100M; + + # OPTIONS preflight handler + if ($request_method = OPTIONS) { + return 204; } - # All dynamic endpoints go through FastCGI - location ~ ^/(upload|list|delete|mirror|media|report|health|admin) { + # PUT /upload - File uploads + location = /upload { include fastcgi_params; fastcgi_pass unix:/tmp/ginxsom.sock; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - - # Handle large uploads - client_max_body_size 100M; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; fastcgi_read_timeout 300s; - - # Disable buffering for large uploads fastcgi_request_buffering off; fastcgi_buffering off; } - # Blob serving - SHA256 hashes - location ~ "^/([a-fA-F0-9]{64})" { + # GET /list/ - List user blobs + location ~ "^/list/([a-f0-9]{64})$" { include fastcgi_params; fastcgi_pass unix:/tmp/ginxsom.sock; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # PUT /mirror - Mirror content + location = /mirror { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # PUT /report - Report content + location = /report { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # GET /auth - NIP-42 challenges + location = /auth { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # GET /health - Health check + location = /health { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # Admin API + location /api/ { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # Admin interface + location /admin { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + } + + # Blob serving - SHA256 patterns (serve directly from filesystem) + location ~ "^/([a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" { + # Handle DELETE via rewrite to internal location + if ($request_method = DELETE) { + rewrite ^/(.*)$ /fcgi-delete/$1 last; + } - # Enable range requests for video streaming - add_header Accept-Ranges bytes always; + # Handle HEAD via rewrite to internal location + if ($request_method = HEAD) { + rewrite ^/(.*)$ /fcgi-head/$1 last; + } - # Cache static blobs - expires 1y; - add_header Cache-Control "public, immutable" always; + # GET requests - serve directly from filesystem + if ($request_method != GET) { + return 405; + } + + try_files /blobs/$1.txt /blobs/$1.jpg /blobs/$1.jpeg /blobs/$1.png /blobs/$1.webp /blobs/$1.gif /blobs/$1.pdf /blobs/$1.mp4 /blobs/$1.mp3 /blobs/$1.md =404; + + # Cache control for blobs + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Internal FastCGI handler for DELETE + location ~ "^/fcgi-delete/([a-f0-9]{64}).*$" { + internal; + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + fastcgi_param REQUEST_URI /$1; + } + + # Internal FastCGI handler for HEAD + location ~ "^/fcgi-head/([a-f0-9]{64}).*$" { + internal; + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; + fastcgi_param REQUEST_URI /$1; + } + + # Root endpoint - server info + location = / { + include fastcgi_params; + fastcgi_pass unix:/tmp/ginxsom.sock; + fastcgi_param SCRIPT_FILENAME /home/teknari/Storage/Blossom/ginxsom; } # Deny access to sensitive files diff --git a/deploy_local.sh b/deploy_local.sh index 62e26a2..7ba2a6c 100755 --- a/deploy_local.sh +++ b/deploy_local.sh @@ -87,6 +87,10 @@ if [ -f "$SCRIPT_DIR/.admin_keys" ]; then chmod 600 "$DEPLOY_DIR/.admin_keys" echo " - .admin_keys" fi +if [ -f "$SCRIPT_DIR/config/fastcgi_params" ]; then + cp "$SCRIPT_DIR/config/fastcgi_params" "$DEPLOY_DIR/" + echo " - fastcgi_params" +fi echo -e "${GREEN}✓ Configuration files copied${NC}" echo "" diff --git a/src/ginxsom.h b/src/ginxsom.h index fbc844b..4b924c9 100644 --- a/src/ginxsom.h +++ b/src/ginxsom.h @@ -10,8 +10,8 @@ // Version information (auto-updated by build system) #define VERSION_MAJOR 0 #define VERSION_MINOR 1 -#define VERSION_PATCH 26 -#define VERSION "v0.1.26" +#define VERSION_PATCH 27 +#define VERSION "v0.1.27" #include #include diff --git a/src/main.c b/src/main.c index 5ad85bc..14f6843 100644 --- a/src/main.c +++ b/src/main.c @@ -973,11 +973,9 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) { sqlite3_stmt *stmt; int rc; - fprintf(stderr, "get_blob_metadata: Looking for %s in database %s\n", sha256, g_db_path); - - rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL); + rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_CREATE, NULL); if (rc) { - fprintf(stderr, "get_blob_metadata: Can't open database %s: %s\n", g_db_path, sqlite3_errmsg(db)); + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); return 0; } @@ -986,7 +984,7 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) { rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { - fprintf(stderr, "get_blob_metadata: SQL error: %s\n", sqlite3_errmsg(db)); + fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); return 0; } @@ -994,10 +992,8 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) { sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); - fprintf(stderr, "get_blob_metadata: sqlite3_step returned %d (SQLITE_ROW=%d, SQLITE_DONE=%d)\n", rc, SQLITE_ROW, SQLITE_DONE); if (rc == SQLITE_ROW) { - fprintf(stderr, "get_blob_metadata: Found blob in database\n"); strncpy(metadata->sha256, (char *)sqlite3_column_text(stmt, 0), MAX_SHA256_LEN - 1); metadata->size = sqlite3_column_int64(stmt, 1); @@ -1096,70 +1092,6 @@ void handle_head_request(const char *sha256) { // HEAD request - no body content } -// Handle GET request for blob - serve the actual file -void handle_get_request(const char *sha256) { - blob_metadata_t metadata = {0}; - - // Debug logging - fprintf(stderr, "GET: Requesting blob %s\n", sha256); - fprintf(stderr, "GET: Database path: %s\n", g_db_path); - fprintf(stderr, "GET: Storage dir: %s\n", g_storage_dir); - - // Validate SHA-256 format (64 hex characters) - if (strlen(sha256) != 64) { - printf("Status: 400 Bad Request\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Invalid SHA-256 hash format\n"); - return; - } - - // Check if blob exists in database - if (!get_blob_metadata(sha256, &metadata)) { - fprintf(stderr, "GET: Blob not found in database\n"); - printf("Status: 404 Not Found\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Blob not found\n"); - return; - } - - // Construct file path - char filepath[4096]; - const char *extension = mime_to_extension(metadata.type); - snprintf(filepath, sizeof(filepath), "%s/%s%s", g_storage_dir, sha256, extension); - - // Open and serve the file - FILE *file = fopen(filepath, "rb"); - if (!file) { - printf("Status: 404 Not Found\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Blob file not found on disk\n"); - return; - } - - // Send headers - printf("Status: 200 OK\r\n"); - printf("Content-Type: %s\r\n", metadata.type); - printf("Content-Length: %ld\r\n", metadata.size); - printf("Cache-Control: public, max-age=31536000, immutable\r\n"); - printf("ETag: \"%s\"\r\n", metadata.sha256); - printf("X-Ginxsom-Server: FastCGI\r\n"); - - if (strlen(metadata.filename) > 0) { - printf("X-Original-Filename: %s\r\n", metadata.filename); - } - - printf("\r\n"); - - // Send file content - char buffer[8192]; - size_t bytes_read; - while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { - fwrite(buffer, 1, bytes_read, stdout); - } - - fclose(file); -} - // Extract SHA-256 from request URI (Blossom compliant - ignores any extension) const char *extract_sha256_from_uri(const char *uri) { static char sha256_buffer[MAX_SHA256_LEN]; @@ -2862,9 +2794,6 @@ if (!config_loaded /* && !initialize_server_config() */) { operation = "list"; } else if (strcmp(request_method, "GET") == 0 && strcmp(request_uri, "/auth") == 0) { operation = "challenge"; - } else if (strcmp(request_method, "GET") == 0 && extract_sha256_from_uri(request_uri) != NULL) { - operation = "get_blob"; // Public blob retrieval - no auth required - resource_hash = extract_sha256_from_uri(request_uri); } else if (strcmp(request_method, "DELETE") == 0) { operation = "delete"; resource_hash = extract_sha256_from_uri(request_uri); @@ -3115,17 +3044,6 @@ if (!config_loaded /* && !initialize_server_config() */) { strcmp(request_uri, "/auth") == 0) { // Handle GET /auth requests using the existing handler handle_auth_challenge_request(); - } else if (strcmp(request_method, "GET") == 0) { - // Handle GET / requests for blob retrieval - const char *sha256 = extract_sha256_from_uri(request_uri); - if (sha256) { - handle_get_request(sha256); - log_request("GET", request_uri, "public", 200); - } else { - send_error_response(400, "invalid_hash", "Invalid SHA-256 hash in URI", - "URI must contain a valid 64-character hex hash"); - log_request("GET", request_uri, "none", 400); - } } else if (strcmp(request_method, "DELETE") == 0) { // Handle DELETE / requests with pre-validated auth const char *sha256 = extract_sha256_from_uri(request_uri); diff --git a/src/request_validator.c b/src/request_validator.c index 8b90e95..1ee6334 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -305,25 +305,7 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request, return NOSTR_SUCCESS; } - // Check if this is a GET blob request - allow public blob retrieval - if (request->operation && strcmp(request->operation, "get_blob") == 0) { - result->valid = 1; - result->error_code = NOSTR_SUCCESS; - strcpy(result->reason, "Public blob retrieval - no authentication required"); - validator_debug_log("VALIDATOR_DEBUG: GET blob detected, bypassing authentication\n"); - return NOSTR_SUCCESS; - } - - // Check if this is a HEAD request - allow public metadata retrieval - if (request->operation && (strcmp(request->operation, "head") == 0 || strcmp(request->operation, "head_upload") == 0)) { - result->valid = 1; - result->error_code = NOSTR_SUCCESS; - strcpy(result->reason, "Public HEAD request - no authentication required"); - validator_debug_log("VALIDATOR_DEBUG: HEAD request detected, bypassing authentication\n"); - return NOSTR_SUCCESS; - } - - // Check if authentication header is provided (required for non-public operations) + // Check if authentication header is provided (required for non-report operations) if (!request->auth_header) { result->valid = 0;