/* * Ginxsom Blossom Server - FastCGI Application * Handles HEAD requests and other dynamic operations */ #include #include #include #include #include #include #include #include #define MAX_SHA256_LEN 65 #define MAX_PATH_LEN 512 #define MAX_MIME_LEN 128 // Database path #define DB_PATH "db/ginxsom.db" // Blob metadata structure typedef struct { char sha256[MAX_SHA256_LEN]; long size; char type[MAX_MIME_LEN]; long uploaded_at; char filename[256]; int found; } blob_metadata_t; // Get blob metadata from database int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) { sqlite3* db; sqlite3_stmt* stmt; int rc; printf("DEBUG: get_blob_metadata() called with sha256='%s'\r\n", sha256); printf("DEBUG: Opening database at path: %s\r\n", DB_PATH); rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); if (rc) { printf("DEBUG: Database open FAILED: %s\r\n", sqlite3_errmsg(db)); fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); return 0; } printf("DEBUG: Database opened successfully\r\n"); const char* sql = "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE sha256 = ?"; printf("DEBUG: Preparing SQL: %s\r\n", sql); rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { printf("DEBUG: SQL prepare FAILED: %s\r\n", sqlite3_errmsg(db)); fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); return 0; } printf("DEBUG: SQL prepared successfully\r\n"); printf("DEBUG: Binding parameter sha256='%s'\r\n", sha256); sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); printf("DEBUG: Executing SQL query...\r\n"); rc = sqlite3_step(stmt); printf("DEBUG: sqlite3_step() returned: %d (SQLITE_ROW=%d, SQLITE_DONE=%d)\r\n", rc, SQLITE_ROW, SQLITE_DONE); if (rc == SQLITE_ROW) { printf("DEBUG: Row found! Extracting metadata...\r\n"); strncpy(metadata->sha256, (char*)sqlite3_column_text(stmt, 0), MAX_SHA256_LEN-1); metadata->size = sqlite3_column_int64(stmt, 1); strncpy(metadata->type, (char*)sqlite3_column_text(stmt, 2), MAX_MIME_LEN-1); metadata->uploaded_at = sqlite3_column_int64(stmt, 3); const char* filename = (char*)sqlite3_column_text(stmt, 4); if (filename) { strncpy(metadata->filename, filename, 255); } else { metadata->filename[0] = '\0'; } metadata->found = 1; printf("DEBUG: Metadata extracted - size=%ld, type='%s'\r\n", metadata->size, metadata->type); } else { printf("DEBUG: No row found for sha256='%s'\r\n", sha256); metadata->found = 0; } sqlite3_finalize(stmt); sqlite3_close(db); printf("DEBUG: Database closed, returning %d\r\n", metadata->found); return metadata->found; } // Check if physical file exists (with extension based on MIME type) int file_exists_with_type(const char* sha256, const char* mime_type) { char filepath[MAX_PATH_LEN]; const char* extension = ""; // Determine file extension based on MIME type if (strstr(mime_type, "image/jpeg")) { extension = ".jpg"; } else if (strstr(mime_type, "image/webp")) { extension = ".webp"; } else if (strstr(mime_type, "image/png")) { extension = ".png"; } else if (strstr(mime_type, "image/gif")) { extension = ".gif"; } else if (strstr(mime_type, "video/mp4")) { extension = ".mp4"; } else if (strstr(mime_type, "video/webm")) { extension = ".webm"; } else if (strstr(mime_type, "audio/mpeg")) { extension = ".mp3"; } else if (strstr(mime_type, "audio/ogg")) { extension = ".ogg"; } else if (strstr(mime_type, "text/plain")) { extension = ".txt"; } snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); printf("DEBUG: file_exists_with_type() checking path: '%s' (MIME: %s)\r\n", filepath, mime_type); struct stat st; int result = stat(filepath, &st); printf("DEBUG: stat() returned: %d (0=success, -1=fail)\r\n", result); if (result == 0) { printf("DEBUG: File exists! Size: %ld bytes\r\n", st.st_size); return 1; } else { printf("DEBUG: File does not exist or stat failed\r\n"); return 0; } } // Handle HEAD request for blob void handle_head_request(const char* sha256) { blob_metadata_t metadata = {0}; printf("DEBUG: handle_head_request called with sha256=%s\r\n", sha256); // Validate SHA-256 format (64 hex characters) if (strlen(sha256) != 64) { printf("DEBUG: SHA-256 length validation failed: %zu\r\n", strlen(sha256)); printf("Status: 400 Bad Request\r\n"); printf("Content-Type: text/plain\r\n\r\n"); printf("Invalid SHA-256 hash format\n"); return; } printf("DEBUG: SHA-256 length validation passed\r\n"); // Check if blob exists in database - this is the single source of truth if (!get_blob_metadata(sha256, &metadata)) { printf("DEBUG: Database lookup failed for sha256=%s\r\n", sha256); printf("Status: 404 Not Found\r\n"); printf("Content-Type: text/plain\r\n\r\n"); printf("Blob not found\n"); return; } printf("DEBUG: Database lookup succeeded - blob exists\r\n"); // Return successful HEAD response with metadata from database 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); // Add timing header for debugging printf("X-Ginxsom-Server: FastCGI\r\n"); printf("X-Ginxsom-Timestamp: %ld\r\n", time(NULL)); if (strlen(metadata.filename) > 0) { printf("X-Original-Filename: %s\r\n", metadata.filename); } printf("\r\n"); // HEAD request - no body content } // 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]; if (!uri || uri[0] != '/') { return NULL; } const char* start = uri + 1; // Skip leading '/' // Extract exactly 64 hex characters, ignoring anything after (extensions, etc.) int len = 0; for (int i = 0; i < 64 && start[i] != '\0'; i++) { char c = start[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { // If we hit a non-hex character before 64 chars, it's invalid if (len < 64) { return NULL; } break; } sha256_buffer[i] = c; len = i + 1; } // Must be exactly 64 hex characters if (len != 64) { return NULL; } sha256_buffer[64] = '\0'; return sha256_buffer; } int main(void) { while (FCGI_Accept() >= 0) { // DEBUG: Log every request received printf("DEBUG: FastCGI received request\r\n"); const char* request_method = getenv("REQUEST_METHOD"); const char* request_uri = getenv("REQUEST_URI"); // DEBUG: Log request details printf("DEBUG: METHOD=%s, URI=%s\r\n", request_method ? request_method : "NULL", request_uri ? request_uri : "NULL"); if (!request_method || !request_uri) { printf("Status: 400 Bad Request\r\n"); printf("Content-Type: text/plain\r\n\r\n"); printf("Invalid request\n"); continue; } // Handle HEAD requests for blob metadata if (strcmp(request_method, "HEAD") == 0) { const char* sha256 = extract_sha256_from_uri(request_uri); printf("DEBUG: Extracted SHA256=%s\r\n", sha256 ? sha256 : "NULL"); if (sha256) { handle_head_request(sha256); } else { printf("Status: 400 Bad Request\r\n"); printf("Content-Type: text/plain\r\n\r\n"); printf("Invalid SHA-256 hash in URI\n"); } } else { // Other methods not implemented yet printf("Status: 501 Not Implemented\r\n"); printf("Content-Type: text/plain\r\n\r\n"); printf("Method %s not implemented\n", request_method); } } return 0; }