1 Commits

9 changed files with 113 additions and 138 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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/<pubkey> - 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

View File

@@ -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 ""

View File

@@ -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 <stddef.h>
#include <stdint.h>

View File

@@ -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 /<sha256> 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 /<sha256> requests with pre-validated auth
const char *sha256 = extract_sha256_from_uri(request_uri);

View File

@@ -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;