Bud 2 mostly done
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
db/ginxsom.db
BIN
db/ginxsom.db
Binary file not shown.
@@ -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"
|
||||||
|
|||||||
5621
logs/error.log
5621
logs/error.log
File diff suppressed because it is too large
Load Diff
@@ -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}")
|
||||||
|
|||||||
366
src/main.c
366
src/main.c
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user