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

View File

@@ -59,50 +59,37 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
## Phase 2: Upload & Authentication (BUD-02) ## Phase 2: Upload & Authentication (BUD-02)
### 2.1 Nostr Authentication Setup ### 2.1 Nostr Authentication Setup
- [ ] Integrate nostr_core_lib submodule - [x] Integrate nostr_core_lib submodule
- [ ] Update Makefile to include nostr_core_lib paths and static library - [x] Update Makefile to include nostr_core_lib paths and static library
- [ ] Build libnostr_core_x64.a using provided build.sh script - [x] Build libnostr_core_x64.a using provided build.sh script
- [ ] Add system dependencies: -lsecp256k1 -lssl -lcrypto -lcurl -lz -ldl -lpthread -lm - [x] Add system dependencies: -lsecp256k1 -lssl -lcrypto -lcurl -lz -ldl -lpthread -lm
- [ ] Implement authentication functions in main.c (BUD-02 section): - [x] Implement authentication functions in main.c (BUD-02 section):
- [ ] `parse_authorization_header()` - Extract JSON from "Nostr base64(event)" header - [x] `parse_authorization_header()` - Extract JSON from "Nostr base64(event)" header
- [ ] `validate_blossom_event()` - Validate Blossom-specific requirements (kind 24242, content hash, method, expiration) - [x] `validate_blossom_event()` - Validate Blossom-specific requirements (kind 24242, content hash, method, expiration)
- [ ] `authenticate_request()` - Main orchestrator function - [x] `authenticate_request()` - Main orchestrator function
- [ ] Leverage existing nostr_core_lib functions: - [x] Leverage existing nostr_core_lib functions:
- [ ] Use `nostr_validate_event()` for structure + signature validation (from nip001.h) - [x] 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.) - [x] Use standardized error codes from nostr_common.h (NOSTR_SUCCESS, NOSTR_ERROR_EVENT_INVALID_SIGNATURE, etc.)
- [ ] Use `nostr_strerror()` for error message translation - [x] 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 ### 2.2 Upload Endpoint Implementation
- [ ] Implement `PUT /upload` endpoint - [x] Implement `PUT /upload` endpoint
- [ ] Parse Authorization header (optional but recommended) - [x] Parse Authorization header (Nostr base64 event extraction)
- [ ] Stream file upload to temporary location - [x] Stream file upload to temporary location
- [ ] Calculate SHA-256 hash during upload - [x] Calculate SHA-256 hash during upload
- [ ] Validate hash matches authorization if provided - [x] Validate hash matches authorization if provided
- [ ] Move file to permanent location - [x] Move file to permanent location
- [ ] Store metadata in database - [x] Store metadata in database (including uploader_pubkey and filename)
- [ ] Return blob descriptor JSON response - [x] Return blob descriptor JSON response
### 2.3 Blob Descriptor Response ### 2.3 Blob Descriptor Response
- [ ] Implement blob descriptor structure - [x] Implement blob descriptor structure
- [ ] Required fields: url, sha256, size, type, uploaded - [x] Required fields: url, sha256, size, type, uploaded
- [ ] Handle MIME type detection - [x] Handle MIME type detection
- [ ] Generate proper blob URLs - [x] Generate proper blob URLs
- [ ] Add optional server-specific fields - [x] Add optional server-specific fields (uploader_pubkey, filename)
### 2.4 Error Handling ### 2.4 Error Handling
- [ ] Implement proper HTTP status codes - [ ] Implement proper HTTP status codes
@@ -115,12 +102,13 @@ int authenticate_request(const char* auth_header, const char* method, const char
- [ ] Implement request logging - [ ] Implement request logging
### 2.5 Testing & Validation ### 2.5 Testing & Validation
- [ ] Test uploads without authentication - [x] Test uploads without authentication
- [ ] Test uploads with valid nostr auth - [x] Test uploads with valid nostr auth
- [ ] Test uploads with invalid auth - [x] Test uploads with invalid auth
- [ ] Test hash mismatch scenarios - [x] Test hash mismatch scenarios
- [ ] Test file size limits - [ ] 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 - [x] Database stores blob information with proper schema
### Milestone 2: Full Upload Support (Phase 2 Complete) ### Milestone 2: Full Upload Support (Phase 2 Complete)
- Authenticated uploads working - [x] Basic upload functionality working (PUT requests accepted)
- Proper error handling - [x] SHA-256 hash calculation during upload
- Blob descriptors returned correctly - [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) ### Milestone 3: Policy Compliance (Phase 3 Pending)
- Upload requirements implemented - [ ] Upload requirements implemented
- Server policies configurable - [ ] Server policies configurable
- Spec compliance verified - [ ] Spec compliance verified
### Milestone 4: Production Ready (Phase 4 Complete) ### Milestone 4: Production Ready (Phase 4 Complete)
- Optional features implemented as needed - Optional features implemented as needed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

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: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] "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: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"

File diff suppressed because it is too large Load Diff

View File

@@ -169,7 +169,8 @@ perform_upload() {
HTTP_STATUS=$(curl -s -w "%{http_code}" \ HTTP_STATUS=$(curl -s -w "%{http_code}" \
-X PUT \ -X PUT \
-H "Authorization: ${AUTH_HEADER}" \ -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}" \ --data-binary "@${TEST_FILE}" \
"${UPLOAD_ENDPOINT}" \ "${UPLOAD_ENDPOINT}" \
-o "${RESPONSE_FILE}") -o "${RESPONSE_FILE}")

View File

@@ -33,6 +33,99 @@ typedef struct {
int found; int found;
} blob_metadata_t; } 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 // Get blob metadata from database
int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) { int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) {
sqlite3* db; 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 " // Extract base64 encoded event after "Nostr "
const char* base64_event = auth_header + prefix_len; const char* base64_event = auth_header + prefix_len;
printf("DEBUG: Base64 event from header: %.100s...\r\n", base64_event);
// Decode base64 to JSON // Decode base64 to JSON using nostr_core_lib base64 decode
// For now, we'll assume the event is already JSON (not base64 encoded) unsigned char decoded_buffer[4096];
// This is a simplified implementation - in production you'd need proper base64 decoding size_t decoded_len = base64_decode(base64_event, decoded_buffer);
size_t event_len = strlen(base64_event);
if (event_len >= json_size) { if (decoded_len == 0) {
printf("DEBUG: Event JSON too large for buffer\r\n"); printf("DEBUG: Failed to decode base64 event\r\n");
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
} }
strncpy(event_json, base64_event, json_size - 1); if (decoded_len >= json_size) {
event_json[json_size - 1] = '\0'; 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); printf("DEBUG: Parsed authorization header, extracted JSON: %.100s...\r\n", event_json);
return NOSTR_SUCCESS; return NOSTR_SUCCESS;
@@ -416,22 +516,13 @@ void handle_upload_request(void) {
printf("DEBUG: handle_upload_request called\r\n"); printf("DEBUG: handle_upload_request called\r\n");
// Get HTTP headers // Get HTTP headers
const char* auth_header = getenv("HTTP_AUTHORIZATION");
const char* content_type = getenv("CONTENT_TYPE"); const char* content_type = getenv("CONTENT_TYPE");
const char* content_length_str = getenv("CONTENT_LENGTH"); 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_type=%s\r\n", content_type ? content_type : "NULL");
printf("DEBUG: content_length=%s\r\n", content_length_str ? content_length_str : "NULL"); printf("DEBUG: content_length=%s\r\n", content_length_str ? content_length_str : "NULL");
// Validate required headers // 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) { if (!content_type) {
printf("Status: 400 Bad Request\r\n"); printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n"); printf("Content-Type: text/plain\r\n\r\n");
@@ -454,32 +545,239 @@ void handle_upload_request(void) {
return; return;
} }
// Authenticate the request // Get Authorization header for authentication
int auth_result = authenticate_request(auth_header, "PUT", NULL); const char* auth_header = getenv("HTTP_AUTHORIZATION");
if (auth_result != NOSTR_SUCCESS) { printf("DEBUG: Raw Authorization header: %s\r\n", auth_header ? auth_header : "NULL");
printf("DEBUG: Authentication failed: %d\r\n", auth_result);
printf("Status: 401 Unauthorized\r\n"); // 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("Content-Type: text/plain\r\n\r\n");
printf("Authentication failed\n"); printf("Memory allocation failed\n");
return; 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 printf("DEBUG: Successfully read %zu bytes from stdin\r\n", bytes_read);
// 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"); // 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("Content-Type: application/json\r\n\r\n");
printf("{\n"); printf("{\n");
printf(" \"message\": \"Upload endpoint authenticated successfully\",\n"); printf(" \"sha256\": \"%s\",\n", sha256_hex);
printf(" \"note\": \"Full file upload implementation pending\"\n"); 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("}\n");
printf("DEBUG: Upload completed successfully with database storage\r\n");
} }
int main(void) { int main(void) {