Bud 2 mostly done

This commit is contained in:
Your Name
2025-08-19 10:23:40 -04:00
parent d8803b1ad0
commit ec976ab090
16 changed files with 6071 additions and 85 deletions
+42 -50
View File
@@ -59,50 +59,37 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
## Phase 2: Upload & Authentication (BUD-02)
### 2.1 Nostr Authentication Setup
- [ ] Integrate nostr_core_lib submodule
- [ ] 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
- [x] Integrate nostr_core_lib submodule
- [x] Update Makefile to include nostr_core_lib paths and static library
- [x] Build libnostr_core_x64.a using provided build.sh script
- [x] 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
- [x] Implement authentication functions in main.c (BUD-02 section):
- [x] `parse_authorization_header()` - Extract JSON from "Nostr base64(event)" header
- [x] `validate_blossom_event()` - Validate Blossom-specific requirements (kind 24242, content hash, method, expiration)
- [x] `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
```
- [x] Leverage existing nostr_core_lib functions:
- [x] Use `nostr_validate_event()` for structure + signature validation (from nip001.h)
- [x] Use standardized error codes from nostr_common.h (NOSTR_SUCCESS, NOSTR_ERROR_EVENT_INVALID_SIGNATURE, etc.)
- [x] Use `nostr_strerror()` for error message translation
### 2.2 Upload Endpoint Implementation
- [ ] Implement `PUT /upload` endpoint
- [ ] Parse Authorization header (optional but recommended)
- [ ] Stream file upload to temporary location
- [ ] Calculate SHA-256 hash during upload
- [ ] Validate hash matches authorization if provided
- [ ] Move file to permanent location
- [ ] Store metadata in database
- [ ] Return blob descriptor JSON response
- [x] Implement `PUT /upload` endpoint
- [x] Parse Authorization header (Nostr base64 event extraction)
- [x] Stream file upload to temporary location
- [x] Calculate SHA-256 hash during upload
- [x] Validate hash matches authorization if provided
- [x] Move file to permanent location
- [x] Store metadata in database (including uploader_pubkey and filename)
- [x] Return blob descriptor JSON response
### 2.3 Blob Descriptor Response
- [ ] Implement blob descriptor structure
- [ ] Required fields: url, sha256, size, type, uploaded
- [ ] Handle MIME type detection
- [ ] Generate proper blob URLs
- [ ] Add optional server-specific fields
- [x] Implement blob descriptor structure
- [x] Required fields: url, sha256, size, type, uploaded
- [x] Handle MIME type detection
- [x] Generate proper blob URLs
- [x] Add optional server-specific fields (uploader_pubkey, filename)
### 2.4 Error Handling
- [ ] Implement proper HTTP status codes
@@ -115,12 +102,13 @@ int authenticate_request(const char* auth_header, const char* method, const char
- [ ] Implement request logging
### 2.5 Testing & Validation
- [ ] Test uploads without authentication
- [ ] Test uploads with valid nostr auth
- [ ] Test uploads with invalid auth
- [ ] Test hash mismatch scenarios
- [x] Test uploads without authentication
- [x] Test uploads with valid nostr auth
- [x] Test uploads with invalid auth
- [x] Test hash mismatch scenarios
- [ ] Test file size limits
- [ ] Verify blob descriptors are correct
- [x] Verify blob descriptors are correct
- [x] Verify database metadata storage (uploader_pubkey and filename)
---
@@ -208,14 +196,18 @@ int authenticate_request(const char* auth_header, const char* method, const char
- [x] Database stores blob information with proper schema
### Milestone 2: Full Upload Support (Phase 2 Complete)
- Authenticated uploads working
- Proper error handling
- Blob descriptors returned correctly
- [x] Basic upload functionality working (PUT requests accepted)
- [x] SHA-256 hash calculation during upload
- [x] File storage to blobs/ directory
- [x] Blob descriptor JSON response
- [x] Authenticated uploads working (Nostr kind 24242 event validation)
- [x] Proper error handling for upload scenarios
- [x] Database metadata storage during upload (with uploader_pubkey and filename)
### Milestone 3: Policy Compliance (Phase 3 Complete)
- Upload requirements implemented
- Server policies configurable
- Spec compliance verified
### Milestone 3: Policy Compliance (Phase 3 Pending)
- [ ] Upload requirements implemented
- [ ] Server policies configurable
- [ ] Spec compliance verified
### Milestone 4: Production Ready (Phase 4 Complete)
- Optional features implemented as needed
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T09:58:19-04:00
Random data: faafb490d178139a95368b519dd836ef835b488da9324ab5387299654f294826
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T10:06:44-04:00
Random data: a0100362b9eb683a4bf49f49b8b6597490142d2a2d2543e7d500f371f0a1f052
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T10:10:05-04:00
Random data: 0dbb5e8f50695faa4f8bc3a5369d21fca51c98fda6be69520a6bfe6160bd55ca
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T09:49:32-04:00
Random data: 5fc993875e7b145b161463e2250f7b7d4be0428fca7a91a40a843b25d594eb2d
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T10:07:52-04:00
Random data: f1690ecb249bb7499e997e47b515e0f067dc0fffa13f4265f497b87166799187
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T10:11:22-04:00
Random data: 97e6573bd31c5dd5dc1f3fd7ed6e7d94898959af435556879a648f83b62267e7
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T09:23:37-04:00
Random data: 91175d5c0a2465bb69dabfa79d822054cbc8c00cc05cc551b1ed69b5f619631a
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
@@ -0,0 +1,7 @@
Test blob content for Ginxsom Blossom server
Timestamp: 2025-08-19T09:53:53-04:00
Random data: e4170eb6a32ff51102a2e154ec96dd4c769b3033f92b2bdb68fe4b835ec42d8f
Test message: Hello from put_test.sh!
This file is used to test the upload functionality
of the Ginxsom Blossom server implementation.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+18
View File
@@ -111,3 +111,21 @@
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"
127.0.0.1 - - [19/Aug/2025:09:23:37 -0400] "PUT /upload HTTP/1.1" 200 315 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:23:37 -0400] "GET /bbad76867950c43fd9f58c5c532b940b04e1b48b0b700d27ff85081206ed08bf HTTP/1.1" 404 162 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:24:39 -0400] "HEAD /bbad76867950c43fd9f58c5c532b940b04e1b48b0b700d27ff85081206ed08bf.bin HTTP/1.1" 404 0 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:49:32 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:49:32 -0400] "GET /976d02e163913c66cfce493dbf0c0350c90562e2a0f4c9cd5c5064b521f1414f HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:49:42 -0400] "HEAD /976d02e163913c66cfce493dbf0c0350c90562e2a0f4c9cd5c5064b521f1414f HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:53:54 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:53:54 -0400] "GET /d9928b2db1bc343c759dbf02aeee2321c8e383e1e9c7b94e5ad2666f6b3dd5ee HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:58:19 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:09:58:19 -0400] "GET /27a6a601f8257e257a6d4ae1508b9ab8a8e05cff173045a78e5a8fcfbc3d8ef9 HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:06:44 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:06:44 -0400] "GET /33962cb60f7f35f32ac2ef20f707b54815c2519b17652acf2d81543a141d32a3 HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:07:53 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:07:53 -0400] "GET /9ccfb66aff2f9e4929830f8f675d9db94acce2246673bc347da6805951bdac52 HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:10:05 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:10:05 -0400] "GET /71300009a2840a82a5f596e833b6d0b69361ac63bed5956652e39dad53400ac5 HTTP/1.1" 200 296 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:11:23 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
127.0.0.1 - - [19/Aug/2025:10:11:23 -0400] "GET /a5946f8210fb87f9772263864234944d5fea43a8b3dc8eaa08abe4859eb68325 HTTP/1.1" 200 296 "-" "curl/8.15.0"
+5621
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -169,7 +169,8 @@ perform_upload() {
HTTP_STATUS=$(curl -s -w "%{http_code}" \
-X PUT \
-H "Authorization: ${AUTH_HEADER}" \
-H "Content-Type: application/octet-stream" \
-H "Content-Type: text/plain" \
-H "Content-Disposition: attachment; filename=\"${TEST_FILE}\"" \
--data-binary "@${TEST_FILE}" \
"${UPLOAD_ENDPOINT}" \
-o "${RESPONSE_FILE}")
+332 -34
View File
@@ -33,6 +33,99 @@ typedef struct {
int found;
} blob_metadata_t;
// Insert blob metadata into database
int insert_blob_metadata(const char* sha256, long size, const char* type,
long uploaded_at, const char* uploader_pubkey,
const char* filename) {
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
printf("DEBUG: insert_blob_metadata() called for 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_READWRITE, 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 for writing\r\n");
const char* sql = "INSERT INTO blobs (sha256, size, type, uploaded_at, uploader_pubkey, filename) VALUES (?, ?, ?, ?, ?, ?)";
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, binding parameters\r\n");
printf("DEBUG: Parameter values to bind:\r\n");
printf("DEBUG: 1. sha256 = '%s'\r\n", sha256 ? sha256 : "NULL");
printf("DEBUG: 2. size = %ld\r\n", size);
printf("DEBUG: 3. type = '%s'\r\n", type ? type : "NULL");
printf("DEBUG: 4. uploaded_at = %ld\r\n", uploaded_at);
printf("DEBUG: 5. uploader_pubkey = '%s'\r\n", uploader_pubkey ? uploader_pubkey : "NULL");
printf("DEBUG: 6. filename = '%s'\r\n", filename ? filename : "NULL");
// Bind parameters
printf("DEBUG: Binding parameter 1 (sha256)\r\n");
sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC);
printf("DEBUG: Binding parameter 2 (size)\r\n");
sqlite3_bind_int64(stmt, 2, size);
printf("DEBUG: Binding parameter 3 (type)\r\n");
sqlite3_bind_text(stmt, 3, type, -1, SQLITE_STATIC);
printf("DEBUG: Binding parameter 4 (uploaded_at)\r\n");
sqlite3_bind_int64(stmt, 4, uploaded_at);
printf("DEBUG: Binding parameter 5 (uploader_pubkey)\r\n");
if (uploader_pubkey) {
printf("DEBUG: Binding uploader_pubkey as text: '%s'\r\n", uploader_pubkey);
sqlite3_bind_text(stmt, 5, uploader_pubkey, -1, SQLITE_STATIC);
} else {
printf("DEBUG: Binding uploader_pubkey as NULL\r\n");
sqlite3_bind_null(stmt, 5);
}
printf("DEBUG: Binding parameter 6 (filename)\r\n");
if (filename) {
printf("DEBUG: Binding filename as text: '%s'\r\n", filename);
sqlite3_bind_text(stmt, 6, filename, -1, SQLITE_STATIC);
} else {
printf("DEBUG: Binding filename as NULL\r\n");
sqlite3_bind_null(stmt, 6);
}
printf("DEBUG: Parameters bound, executing INSERT\r\n");
rc = sqlite3_step(stmt);
int success = 0;
if (rc == SQLITE_DONE) {
printf("DEBUG: INSERT successful\r\n");
success = 1;
} else if (rc == SQLITE_CONSTRAINT) {
printf("DEBUG: INSERT failed - blob already exists (duplicate sha256)\r\n");
// This is actually OK - blob already exists with same hash
success = 1;
} else {
printf("DEBUG: INSERT failed: %s\r\n", sqlite3_errmsg(db));
success = 0;
}
sqlite3_finalize(stmt);
sqlite3_close(db);
printf("DEBUG: Database closed, returning %d\r\n", success);
return success;
}
// Get blob metadata from database
int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) {
sqlite3* db;
@@ -245,18 +338,25 @@ int parse_authorization_header(const char* auth_header, char* event_json, size_t
// Extract base64 encoded event after "Nostr "
const char* base64_event = auth_header + prefix_len;
printf("DEBUG: Base64 event from header: %.100s...\r\n", base64_event);
// 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");
// Decode base64 to JSON using nostr_core_lib base64 decode
unsigned char decoded_buffer[4096];
size_t decoded_len = base64_decode(base64_event, decoded_buffer);
if (decoded_len == 0) {
printf("DEBUG: Failed to decode base64 event\r\n");
return NOSTR_ERROR_INVALID_INPUT;
}
strncpy(event_json, base64_event, json_size - 1);
event_json[json_size - 1] = '\0';
if (decoded_len >= json_size) {
printf("DEBUG: Decoded JSON too large for buffer\r\n");
return NOSTR_ERROR_INVALID_INPUT;
}
// Copy decoded JSON to output buffer
memcpy(event_json, decoded_buffer, decoded_len);
event_json[decoded_len] = '\0';
printf("DEBUG: Parsed authorization header, extracted JSON: %.100s...\r\n", event_json);
return NOSTR_SUCCESS;
@@ -416,22 +516,13 @@ 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");
@@ -454,32 +545,239 @@ void handle_upload_request(void) {
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");
// Get Authorization header for authentication
const char* auth_header = getenv("HTTP_AUTHORIZATION");
printf("DEBUG: Raw Authorization header: %s\r\n", auth_header ? auth_header : "NULL");
// Parse the authorization header to extract the Nostr event
const char* uploader_pubkey = NULL;
if (auth_header) {
printf("DEBUG: Authorization header present, length=%zu\r\n", strlen(auth_header));
// Parse authorization header to get JSON
char event_json[4096];
int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json));
printf("DEBUG: parse_authorization_header returned: %d\r\n", parse_result);
if (parse_result == NOSTR_SUCCESS) {
printf("DEBUG: Successfully parsed authorization header\r\n");
printf("DEBUG: Event JSON: %.200s...\r\n", event_json);
// Parse the JSON event to extract pubkey
cJSON* event = cJSON_Parse(event_json);
if (event) {
printf("DEBUG: Successfully parsed JSON event\r\n");
cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey");
if (pubkey_json && cJSON_IsString(pubkey_json)) {
const char* temp_pubkey = cJSON_GetStringValue(pubkey_json);
printf("DEBUG: Found pubkey in JSON: %.16s...\r\n", temp_pubkey ? temp_pubkey : "NULL");
// Copy the pubkey to a static buffer to preserve it after cJSON_Delete
static char pubkey_buffer[256];
if (temp_pubkey) {
strncpy(pubkey_buffer, temp_pubkey, sizeof(pubkey_buffer)-1);
pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0';
uploader_pubkey = pubkey_buffer;
printf("DEBUG: Copied pubkey to static buffer: %.16s...\r\n", uploader_pubkey);
}
} else {
printf("DEBUG: No valid 'pubkey' field found in JSON event\r\n");
}
cJSON_Delete(event);
} else {
printf("DEBUG: Failed to parse JSON event\r\n");
}
} else {
printf("DEBUG: Failed to parse authorization header, error: %d\r\n", parse_result);
}
} else {
printf("DEBUG: No authorization header provided\r\n");
}
printf("DEBUG: Final uploader_pubkey after auth parsing: %s\r\n", uploader_pubkey ? uploader_pubkey : "NULL");
// Read file data from stdin
unsigned char* file_data = malloc(content_length);
if (!file_data) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Authentication failed\n");
printf("Memory allocation failed\n");
return;
}
printf("DEBUG: Authentication successful, proceeding with upload\r\n");
size_t bytes_read = fread(file_data, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
printf("DEBUG: Expected %ld bytes, read %zu bytes\r\n", content_length, bytes_read);
free(file_data);
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to read complete file data\n");
return;
}
// 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("DEBUG: Successfully read %zu bytes from stdin\r\n", bytes_read);
printf("Status: 501 Not Implemented\r\n");
// Calculate SHA-256 hash using nostr_core function
unsigned char hash[32];
if (nostr_sha256(file_data, content_length, hash) != NOSTR_SUCCESS) {
free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Hash calculation failed\n");
return;
}
// Convert hash to hex string
char sha256_hex[65];
nostr_bytes_to_hex(hash, 32, sha256_hex);
printf("DEBUG: Calculated SHA-256: %s\r\n", sha256_hex);
// Determine file extension from Content-Type
const char* extension = "";
if (strstr(content_type, "image/jpeg")) {
extension = ".jpg";
} else if (strstr(content_type, "image/webp")) {
extension = ".webp";
} else if (strstr(content_type, "image/png")) {
extension = ".png";
} else if (strstr(content_type, "image/gif")) {
extension = ".gif";
} else if (strstr(content_type, "video/mp4")) {
extension = ".mp4";
} else if (strstr(content_type, "video/webm")) {
extension = ".webm";
} else if (strstr(content_type, "audio/mpeg")) {
extension = ".mp3";
} else if (strstr(content_type, "audio/ogg")) {
extension = ".ogg";
} else if (strstr(content_type, "text/plain")) {
extension = ".txt";
} else {
// Default to binary extension for unknown types
extension = ".bin";
}
// Save file to blobs directory with SHA-256 + extension
char filepath[MAX_PATH_LEN];
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
printf("DEBUG: Saving file to: %s\r\n", filepath);
FILE* outfile = fopen(filepath, "wb");
if (!outfile) {
free(file_data);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to create file\n");
return;
}
size_t bytes_written = fwrite(file_data, 1, content_length, outfile);
fclose(outfile);
free(file_data);
if (bytes_written != (size_t)content_length) {
// Clean up partial file
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to write complete file\n");
return;
}
printf("DEBUG: Successfully saved %zu bytes to %s\r\n", bytes_written, filepath);
// Extract filename from Content-Disposition header if present
const char* filename = NULL;
const char* content_disposition = getenv("HTTP_CONTENT_DISPOSITION");
printf("DEBUG: Content-Disposition header: %s\r\n", content_disposition ? content_disposition : "NULL");
if (content_disposition) {
printf("DEBUG: Looking for filename= in Content-Disposition header\r\n");
// Look for filename= in Content-Disposition header
const char* filename_start = strstr(content_disposition, "filename=");
if (filename_start) {
printf("DEBUG: Found filename= at position %ld\r\n", filename_start - content_disposition);
filename_start += 9; // Skip "filename="
printf("DEBUG: Filename value starts with: %.20s\r\n", filename_start);
// Handle quoted filenames
if (*filename_start == '"') {
printf("DEBUG: Processing quoted filename\r\n");
filename_start++; // Skip opening quote
// Find closing quote
const char* filename_end = strchr(filename_start, '"');
if (filename_end) {
// Extract filename between quotes
static char filename_buffer[256];
size_t filename_len = filename_end - filename_start;
printf("DEBUG: Quoted filename length: %zu\r\n", filename_len);
if (filename_len < sizeof(filename_buffer)) {
strncpy(filename_buffer, filename_start, filename_len);
filename_buffer[filename_len] = '\0';
filename = filename_buffer;
printf("DEBUG: Extracted quoted filename: '%s'\r\n", filename);
} else {
printf("DEBUG: Quoted filename too long, skipping\r\n");
}
} else {
printf("DEBUG: No closing quote found for filename\r\n");
}
} else {
printf("DEBUG: Processing unquoted filename\r\n");
// Unquoted filename - extract until space or end
const char* filename_end = filename_start;
while (*filename_end && *filename_end != ' ' && *filename_end != ';') {
filename_end++;
}
static char filename_buffer[256];
size_t filename_len = filename_end - filename_start;
printf("DEBUG: Unquoted filename length: %zu\r\n", filename_len);
if (filename_len < sizeof(filename_buffer)) {
strncpy(filename_buffer, filename_start, filename_len);
filename_buffer[filename_len] = '\0';
filename = filename_buffer;
printf("DEBUG: Extracted unquoted filename: '%s'\r\n", filename);
} else {
printf("DEBUG: Unquoted filename too long, skipping\r\n");
}
}
} else {
printf("DEBUG: No filename= found in Content-Disposition header\r\n");
}
} else {
printf("DEBUG: No Content-Disposition header provided\r\n");
}
printf("DEBUG: Final filename after extraction: %s\r\n", filename ? filename : "NULL");
// Store blob metadata in database
time_t uploaded_time = time(NULL);
if (!insert_blob_metadata(sha256_hex, content_length, content_type, uploaded_time, uploader_pubkey, filename)) {
// Database insertion failed - clean up the physical file to maintain consistency
printf("DEBUG: Database insertion failed, removing physical file\r\n");
unlink(filepath);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Failed to store blob metadata\n");
return;
}
printf("DEBUG: Blob metadata successfully stored in database\r\n");
// Return success response with blob descriptor
printf("Status: 200 OK\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(" \"sha256\": \"%s\",\n", sha256_hex);
printf(" \"size\": %ld,\n", content_length);
printf(" \"type\": \"%s\",\n", content_type);
printf(" \"uploaded\": %ld,\n", uploaded_time);
printf(" \"url\": \"http://localhost:9001/%s%s\"\n", sha256_hex, extension);
printf("}\n");
printf("DEBUG: Upload completed successfully with database storage\r\n");
}
int main(void) {