Start upload functionality

This commit is contained in:
Your Name
2025-08-19 08:00:40 -04:00
parent e8bac95aca
commit d8803b1ad0
16 changed files with 2386 additions and 42 deletions

View File

@@ -36,6 +36,8 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
- [x] Handle 404s gracefully when blob doesn't exist
- [x] Configure FastCGI pass-through for HEAD and non-GET requests
**Future Enhancement Note**: Consider implementing nginx Lua extension for true Blossom compliance with dynamic file discovery. The current approach uses explicit extension lists in `try_files`, which works well for common extensions but may not serve files with unusual extensions. Lua module would allow runtime directory scanning for hash-matching files regardless of extension.
### 1.4 Basic HEAD Endpoint
- [x] Implement FastCGI handler for `HEAD /<sha256>`
- [x] Query database for blob metadata (single source of truth)
@@ -58,12 +60,32 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
### 2.1 Nostr Authentication Setup
- [ ] Integrate nostr_core_lib submodule
- [ ] Implement nostr event validation
- [ ] Verify event signature (schnorr)
- [ ] Validate event structure (kind 24242)
- [ ] Check required fields (t, expiration, x tags)
- [ ] Implement expiration checking
- [ ] Create authentication middleware
- [ ] Update Makefile to include nostr_core_lib paths and static library
- [ ] Build libnostr_core_x64.a using provided build.sh script
- [ ] Add system dependencies: -lsecp256k1 -lssl -lcrypto -lcurl -lz -ldl -lpthread -lm
- [ ] Implement authentication functions in main.c (BUD-02 section):
- [ ] `parse_authorization_header()` - Extract JSON from "Nostr base64(event)" header
- [ ] `validate_blossom_event()` - Validate Blossom-specific requirements (kind 24242, content hash, method, expiration)
- [ ] `authenticate_request()` - Main orchestrator function
- [ ] Leverage existing nostr_core_lib functions:
- [ ] Use `nostr_validate_event()` for structure + signature validation (from nip001.h)
- [ ] Use standardized error codes from nostr_common.h (NOSTR_SUCCESS, NOSTR_ERROR_EVENT_INVALID_SIGNATURE, etc.)
- [ ] Use `nostr_strerror()` for error message translation
**Function Specifications:**
```c
// Custom functions to implement:
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
int validate_blossom_event(struct cJSON* event, const char* expected_hash, const char* method);
int authenticate_request(const char* auth_header, const char* method, const char* file_hash);
// Existing nostr_core_lib functions to use directly:
// - nostr_validate_event(cJSON* event) - handles structure + signature validation
// - nostr_validate_event_structure(cJSON* event) - if separate validation needed
// - nostr_verify_event_signature(cJSON* event) - if separate signature check needed
```
### 2.2 Upload Endpoint Implementation
- [ ] Implement `PUT /upload` endpoint

View File

@@ -1,8 +1,8 @@
# Ginxsom Blossom Server Makefile
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -O2
LIBS = -lfcgi -lsqlite3
CFLAGS = -Wall -Wextra -std=c99 -O2 -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson
LIBS = -lfcgi -lsqlite3 nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -lcurl
SRCDIR = src
BUILDDIR = build
TARGET = $(BUILDDIR)/ginxsom-fcgi

Binary file not shown.

Binary file not shown.

24
config/fastcgi_params Normal file
View File

@@ -0,0 +1,24 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@@ -18,8 +18,8 @@ http {
keepalive_timeout 65;
types_hash_max_size 2048;
# MIME types
include /etc/nginx/mime.types;
# MIME types (local)
include mime.types;
default_type application/octet-stream;
# Logging (relative to prefix directory)
@@ -58,9 +58,8 @@ http {
}
# GET requests served directly with explicit file extensions
# try_files /$1 =404;
# try_files /$1.webp =404;
try_files /$1.pdf /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.mp4 /$1.mp3 =404;
# Potentially in the future look at a LUA extension
try_files /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.txt /$1.md=404;
# Set appropriate headers for blobs
add_header Cache-Control "public, max-age=31536000, immutable";
@@ -68,33 +67,7 @@ http {
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
}
# Commented out problematic regex for reference
# location ~ "^/([a-f0-9]{64}).*$" {
# limit_except HEAD GET {
# deny all;
# }
#
# # Debug headers to see what nginx is capturing
# add_header X-Debug-Hash "$1" always;
# add_header X-Debug-TryFiles "$1*" always;
# add_header X-Debug-URI "$uri" always;
# add_header X-Debug-Root "$document_root" always;
#
# # Route HEAD requests to FastCGI via rewrite
# if ($request_method = HEAD) {
# rewrite ^/(.*)$ /fcgi-head/$1 last;
# }
#
# # GET requests served directly with hash-only lookup
# try_files $1* =404;
#
# # Set appropriate headers for blobs
# add_header Cache-Control "public, max-age=31536000, immutable";
# add_header X-Content-Type-Options nosniff;
# add_header X-Frame-Options DENY;
# add_header X-XSS-Protection "1; mode=block";
# }
# FastCGI handler for HEAD requests
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
@@ -116,6 +89,19 @@ http {
}
# Upload endpoint - requires authentication
location /upload {
# Pass to FastCGI application for processing
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
fastcgi_pass fastcgi_backend;
# Only allow PUT method for uploads
if ($request_method !~ ^(PUT)$ ) {
return 405;
}
}
# Health check endpoint
location /health {
access_log off;

95
config/mime.types Normal file
View File

@@ -0,0 +1,95 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
image/png png;
image/webp webp;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

24
fastcgi_params Normal file
View File

@@ -0,0 +1,24 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@@ -105,3 +105,9 @@
127.0.0.1 - - [18/Aug/2025:22:40:32 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:23:00:31 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [18/Aug/2025:23:01:14 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [19/Aug/2025:06:00:05 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [19/Aug/2025:07:49:21 -0400] "PUT /upload HTTP/1.1" 405 166 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:07:49:21 -0400] "GET /21fc30dac4c95ad4b26eb78d97cb31a0cb4bc69f30c0d4195f7685ac13b22ce6 HTTP/1.1" 404 162 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:07:51:54 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
127.0.0.1 - - [19/Aug/2025:07:57:54 -0400] "PUT /upload HTTP/1.1" 501 38 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:07:57:54 -0400] "GET /ae9f59c7ac386b7fe6343d669fc27f37d7b66256824be655d29a256908f154e9 HTTP/1.1" 404 162 "-" "curl/8.15.0"

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
296457
349770

95
mime.types Normal file
View File

@@ -0,0 +1,95 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
image/png png;
image/webp webp;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

249
put_test.sh Executable file
View File

@@ -0,0 +1,249 @@
#!/bin/bash
# put_test.sh - Test script for Ginxsom Blossom server upload functionality
# This script simulates a user uploading a blob to ginxsom using proper Blossom authentication
set -e # Exit on any error
# Configuration
SERVER_URL="http://localhost:9001"
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
TEST_FILE="test_blob_$(date +%s).txt"
CLEANUP_FILES=()
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Cleanup function
cleanup() {
echo -e "${YELLOW}Cleaning up temporary files...${NC}"
for file in "${CLEANUP_FILES[@]}"; do
if [[ -f "$file" ]]; then
rm -f "$file"
echo "Removed: $file"
fi
done
}
# Set up cleanup on exit
trap cleanup EXIT
# Helper functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Check prerequisites
check_prerequisites() {
log_info "Checking prerequisites..."
# Check if nak is installed
if ! command -v nak &> /dev/null; then
log_error "nak command not found. Please install nak first."
log_info "Install with: go install github.com/fiatjaf/nak@latest"
exit 1
fi
log_success "nak is installed"
# Check if curl is available
if ! command -v curl &> /dev/null; then
log_error "curl command not found. Please install curl."
exit 1
fi
log_success "curl is available"
# Check if sha256sum is available
if ! command -v sha256sum &> /dev/null; then
log_error "sha256sum command not found."
exit 1
fi
log_success "sha256sum is available"
# Check if base64 is available
if ! command -v base64 &> /dev/null; then
log_error "base64 command not found."
exit 1
fi
log_success "base64 is available"
}
# Check if server is running
check_server() {
log_info "Checking if server is running..."
if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then
log_success "Server is running at ${SERVER_URL}"
else
log_error "Server is not responding at ${SERVER_URL}"
log_info "Please start the server with: ./scripts/start-fcgi.sh && nginx -p . -c config/local-nginx.conf"
exit 1
fi
}
# Create test file
create_test_file() {
log_info "Creating test file: ${TEST_FILE}"
# Create test content with timestamp and random data
cat > "${TEST_FILE}" << EOF
Test blob content for Ginxsom Blossom server
Timestamp: $(date -Iseconds)
Random data: $(openssl rand -hex 32)
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
EOF
CLEANUP_FILES+=("${TEST_FILE}")
log_success "Created test file with $(wc -c < "${TEST_FILE}") bytes"
}
# Calculate file hash
calculate_hash() {
log_info "Calculating SHA-256 hash..."
HASH=$(sha256sum "${TEST_FILE}" | cut -d' ' -f1)
log_success "File hash: ${HASH}"
}
# Generate nostr event
generate_nostr_event() {
log_info "Generating kind 24242 nostr event with nak..."
# Calculate expiration time (1 hour from now)
EXPIRATION=$(date -d '+1 hour' +%s)
# Generate the event using nak
EVENT_JSON=$(nak event -k 24242 -c "" \
-t "t=upload" \
-t "x=${HASH}" \
-t "expiration=${EXPIRATION}")
if [[ -z "$EVENT_JSON" ]]; then
log_error "Failed to generate nostr event"
exit 1
fi
log_success "Generated nostr event"
echo "Event JSON: $EVENT_JSON"
}
# Create authorization header
create_auth_header() {
log_info "Creating authorization header..."
# Base64 encode the event (without newlines)
AUTH_B64=$(echo -n "$EVENT_JSON" | base64 -w 0)
AUTH_HEADER="Nostr ${AUTH_B64}"
log_success "Created authorization header"
echo "Auth header length: ${#AUTH_HEADER} characters"
}
# Perform upload
perform_upload() {
log_info "Performing upload to ${UPLOAD_ENDPOINT}..."
# Create temporary file for response
RESPONSE_FILE=$(mktemp)
CLEANUP_FILES+=("${RESPONSE_FILE}")
# Perform the upload with verbose output
HTTP_STATUS=$(curl -s -w "%{http_code}" \
-X PUT \
-H "Authorization: ${AUTH_HEADER}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${TEST_FILE}" \
"${UPLOAD_ENDPOINT}" \
-o "${RESPONSE_FILE}")
echo "HTTP Status: ${HTTP_STATUS}"
echo "Response body:"
cat "${RESPONSE_FILE}"
echo
# Check response
case "${HTTP_STATUS}" in
200)
log_success "Upload successful!"
;;
201)
log_success "Upload successful (created)!"
;;
400)
log_error "Bad request - check the event format"
;;
401)
log_error "Unauthorized - authentication failed"
;;
405)
log_error "Method not allowed - check nginx configuration"
;;
413)
log_error "Payload too large"
;;
501)
log_warning "Upload endpoint not yet implemented (expected for now)"
;;
*)
log_error "Upload failed with HTTP status: ${HTTP_STATUS}"
;;
esac
}
# Test file retrieval
test_retrieval() {
log_info "Testing file retrieval..."
RETRIEVAL_URL="${SERVER_URL}/${HASH}"
if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then
log_success "File can be retrieved at: ${RETRIEVAL_URL}"
else
log_warning "File not yet available for retrieval (expected if upload processing not implemented)"
fi
}
# Main execution
main() {
echo "=== Ginxsom Blossom Upload Test ==="
echo "Timestamp: $(date -Iseconds)"
echo
check_prerequisites
check_server
create_test_file
calculate_hash
generate_nostr_event
create_auth_header
perform_upload
test_retrieval
echo
log_info "Test completed!"
echo "Summary:"
echo " Test file: ${TEST_FILE}"
echo " File hash: ${HASH}"
echo " Server: ${SERVER_URL}"
echo " Upload endpoint: ${UPLOAD_ENDPOINT}"
}
# Run main function
main "$@"

84
src/ginxsom.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* Ginxsom Blossom Server Header
*
* This header contains all function declarations and type definitions
* organized by BUD (Blossom Unified Draft) sections.
*/
#ifndef GINXSOM_H
#define GINXSOM_H
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include <fcgi_stdio.h>
#include <sqlite3.h>
#include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#ifdef __cplusplus
extern "C" {
#endif
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD 01 - Basic Protocol Flow
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Database connection management
extern sqlite3* db;
int init_database(void);
void close_database(void);
// SHA-256 extraction and validation
const char* extract_sha256_from_uri(const char* uri);
// HEAD request handling
void handle_head_request(const char* uri);
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD 02 - Upload & Authentication
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Authorization header parsing
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
// Blossom event validation (specific to kind 24242)
int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method);
// Main authentication orchestrator
int authenticate_request(const char* auth_header, const char* method, const char* file_hash);
// Upload handling
void handle_upload_request(void);
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD 06 - Upload Requirements
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Upload policy management (for future implementation)
void handle_upload_requirements_request(void);
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// HTTP response helpers
void send_error_response(int status_code, const char* message);
void send_json_response(int status_code, const char* json_content);
// Logging utilities
void log_request(const char* method, const char* uri, int status_code);
#ifdef __cplusplus
}
#endif
#endif // GINXSOM_H

View File

@@ -3,14 +3,18 @@
* Handles HEAD requests and other dynamic operations
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcgi_stdio.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <time.h>
#include <stdint.h>
#include "ginxsom.h"
#define MAX_SHA256_LEN 65
#define MAX_PATH_LEN 512
@@ -218,6 +222,266 @@ const char* extract_sha256_from_uri(const char* uri) {
return sha256_buffer;
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// BUD 02 - Upload & Authentication
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Parse Authorization header and extract JSON event
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size) {
if (!auth_header || !event_json) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Check for "Nostr " prefix (case-insensitive)
const char* prefix = "nostr ";
size_t prefix_len = strlen(prefix);
if (strncasecmp(auth_header, prefix, prefix_len) != 0) {
printf("DEBUG: Authorization header missing 'Nostr ' prefix\r\n");
return NOSTR_ERROR_INVALID_INPUT;
}
// Extract base64 encoded event after "Nostr "
const char* base64_event = auth_header + prefix_len;
// Decode base64 to JSON
// For now, we'll assume the event is already JSON (not base64 encoded)
// This is a simplified implementation - in production you'd need proper base64 decoding
size_t event_len = strlen(base64_event);
if (event_len >= json_size) {
printf("DEBUG: Event JSON too large for buffer\r\n");
return NOSTR_ERROR_INVALID_INPUT;
}
strncpy(event_json, base64_event, json_size - 1);
event_json[json_size - 1] = '\0';
printf("DEBUG: Parsed authorization header, extracted JSON: %.100s...\r\n", event_json);
return NOSTR_SUCCESS;
}
// Validate Blossom-specific event requirements (kind 24242)
int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method) {
if (!event) {
return NOSTR_ERROR_INVALID_INPUT;
}
printf("DEBUG: Validating Blossom event\r\n");
// Check event kind (must be 24242 for Blossom uploads)
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
if (!kind_json || !cJSON_IsNumber(kind_json)) {
printf("DEBUG: Event missing or invalid 'kind' field\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
int kind = cJSON_GetNumberValue(kind_json);
if (kind != 24242) {
printf("DEBUG: Event kind %d is not 24242 (Blossom upload)\r\n", kind);
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check that created_at exists (basic validation)
cJSON* created_at_json = cJSON_GetObjectItem(event, "created_at");
if (!created_at_json || !cJSON_IsNumber(created_at_json)) {
printf("DEBUG: Event missing or invalid 'created_at' field\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Look for expiration in tags
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
printf("DEBUG: Event missing or invalid 'tags' field\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
time_t expiration = 0;
int found_method = 0;
int found_hash = 0;
// Parse tags for 't' (method), 'x' (hash), and 'expiration'
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag)) continue;
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name)) continue;
const char* tag_name_str = cJSON_GetStringValue(tag_name);
if (strcmp(tag_name_str, "t") == 0) {
// Method tag
cJSON* method_value = cJSON_GetArrayItem(tag, 1);
if (method_value && cJSON_IsString(method_value)) {
const char* event_method = cJSON_GetStringValue(method_value);
if (strcmp(event_method, method) == 0) {
found_method = 1;
printf("DEBUG: Found matching method tag: %s\r\n", event_method);
}
}
} else if (strcmp(tag_name_str, "x") == 0) {
// Hash tag
cJSON* hash_value = cJSON_GetArrayItem(tag, 1);
if (hash_value && cJSON_IsString(hash_value)) {
const char* event_hash = cJSON_GetStringValue(hash_value);
if (expected_hash && strcmp(event_hash, expected_hash) == 0) {
found_hash = 1;
printf("DEBUG: Found matching hash tag: %s\r\n", event_hash);
}
}
} else if (strcmp(tag_name_str, "expiration") == 0) {
// Expiration tag
cJSON* exp_value = cJSON_GetArrayItem(tag, 1);
if (exp_value && cJSON_IsString(exp_value)) {
expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
printf("DEBUG: Found expiration tag: %ld\r\n", expiration);
}
}
}
// Check if method matches (required)
if (!found_method) {
printf("DEBUG: Event missing or invalid method tag\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check if hash matches (if provided)
if (expected_hash && !found_hash) {
printf("DEBUG: Event hash doesn't match expected hash\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check expiration
time_t now = time(NULL);
if (expiration > 0 && now > expiration) {
printf("DEBUG: Event expired (now: %ld, exp: %ld)\r\n", now, expiration);
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
printf("DEBUG: Blossom event validation passed\r\n");
return NOSTR_SUCCESS;
}
// Main authentication function
int authenticate_request(const char* auth_header, const char* method, const char* file_hash) {
if (!auth_header) {
printf("DEBUG: No authorization header provided\r\n");
return NOSTR_ERROR_INVALID_INPUT;
}
printf("DEBUG: Authenticating request - method: %s, hash: %s\r\n",
method ? method : "null", file_hash ? file_hash : "null");
// Parse authorization header
char event_json[4096];
int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json));
if (parse_result != NOSTR_SUCCESS) {
printf("DEBUG: Authorization header parsing failed: %d\r\n", parse_result);
return parse_result;
}
// Parse JSON event
cJSON* event = cJSON_Parse(event_json);
if (!event) {
printf("DEBUG: Failed to parse JSON event\r\n");
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Validate event structure and signature using nostr_core_lib
int validation_result = nostr_validate_event(event);
if (validation_result != NOSTR_SUCCESS) {
printf("DEBUG: Nostr event validation failed: %d (%s)\r\n",
validation_result, nostr_strerror(validation_result));
cJSON_Delete(event);
return validation_result;
}
// Validate Blossom-specific requirements
int blossom_result = validate_blossom_event(event, file_hash, method);
if (blossom_result != NOSTR_SUCCESS) {
printf("DEBUG: Blossom event validation failed: %d\r\n", blossom_result);
cJSON_Delete(event);
return blossom_result;
}
cJSON_Delete(event);
printf("DEBUG: Authentication successful\r\n");
return NOSTR_SUCCESS;
}
// Handle PUT /upload requests
void handle_upload_request(void) {
printf("DEBUG: handle_upload_request called\r\n");
// Get HTTP headers
const char* auth_header = getenv("HTTP_AUTHORIZATION");
const char* content_type = getenv("CONTENT_TYPE");
const char* content_length_str = getenv("CONTENT_LENGTH");
printf("DEBUG: auth_header=%s\r\n", auth_header ? auth_header : "NULL");
printf("DEBUG: content_type=%s\r\n", content_type ? content_type : "NULL");
printf("DEBUG: content_length=%s\r\n", content_length_str ? content_length_str : "NULL");
// Validate required headers
if (!auth_header) {
printf("Status: 401 Unauthorized\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Authorization header required\n");
return;
}
if (!content_type) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Content-Type header required\n");
return;
}
if (!content_length_str) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Content-Length header required\n");
return;
}
long content_length = atol(content_length_str);
if (content_length <= 0 || content_length > 100 * 1024 * 1024) { // 100MB limit
printf("Status: 413 Payload Too Large\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("File size must be between 1 byte and 100MB\n");
return;
}
// Authenticate the request
int auth_result = authenticate_request(auth_header, "PUT", NULL);
if (auth_result != NOSTR_SUCCESS) {
printf("DEBUG: Authentication failed: %d\r\n", auth_result);
printf("Status: 401 Unauthorized\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Authentication failed\n");
return;
}
printf("DEBUG: Authentication successful, proceeding with upload\r\n");
// For now, return a simple response indicating the upload endpoint is working
// In a full implementation, you would:
// 1. Read the file data from stdin
// 2. Calculate SHA-256 hash
// 3. Save file to blobs/ directory with proper extension
// 4. Store metadata in database
// 5. Return blob descriptor JSON
printf("Status: 501 Not Implemented\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"message\": \"Upload endpoint authenticated successfully\",\n");
printf(" \"note\": \"Full file upload implementation pending\"\n");
printf("}\n");
}
int main(void) {
while (FCGI_Accept() >= 0) {
// DEBUG: Log every request received
@@ -249,6 +513,9 @@ int main(void) {
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash in URI\n");
}
} else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/upload") == 0) {
// Handle PUT /upload requests with authentication
handle_upload_request();
} else {
// Other methods not implemented yet
printf("Status: 501 Not Implemented\r\n");