List Blob Endpoint
This commit is contained in:
@@ -37,3 +37,6 @@
|
||||
- If nginx fails to start, check for port conflicts or kill existing processes
|
||||
- If FastCGI fails, check socket permissions and that binary exists
|
||||
- Always use local paths, never assume system installations
|
||||
|
||||
use ./restart-nginx.sh to restart nginx
|
||||
use ./start-fcgi.sh to start fcgi
|
||||
@@ -92,16 +92,43 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
|
||||
- [x] Add optional server-specific fields (uploader_pubkey, filename)
|
||||
|
||||
### 2.4 Error Handling
|
||||
- [ ] Implement proper HTTP status codes
|
||||
- [ ] 400 Bad Request for invalid data
|
||||
- [ ] 401 Unauthorized for auth failures
|
||||
- [ ] 409 Conflict for hash mismatches
|
||||
- [ ] 413 Payload Too Large for size limits
|
||||
- [ ] 500 Internal Server Error for system issues
|
||||
- [ ] Add detailed error messages
|
||||
- [ ] Implement request logging
|
||||
- [x] Implement proper HTTP status codes
|
||||
- [x] 400 Bad Request for invalid data
|
||||
- [x] 401 Unauthorized for auth failures
|
||||
- [x] 409 Conflict for hash mismatches
|
||||
- [x] 413 Payload Too Large for size limits
|
||||
- [x] 500 Internal Server Error for system issues
|
||||
- [x] Add detailed error messages
|
||||
- [x] Implement request logging
|
||||
|
||||
### 2.5 Testing & Validation
|
||||
### 2.5 List Blobs Endpoint
|
||||
- [ ] Implement `GET /list/<pubkey>` endpoint
|
||||
- [ ] Extract pubkey from URL path
|
||||
- [ ] Query database for blobs uploaded by specified pubkey
|
||||
- [ ] Support `since` and `until` query parameters for date filtering
|
||||
- [ ] Return JSON array of blob descriptors
|
||||
- [ ] Handle empty results gracefully
|
||||
- [ ] Implement optional authorization with kind 24242 event validation
|
||||
- [ ] Validate `t` tag is set to "list"
|
||||
- [ ] Check authorization expiration
|
||||
- [ ] Verify event signature and structure
|
||||
|
||||
### 2.6 Delete Blob Endpoint
|
||||
- [ ] Implement `DELETE /<sha256>` endpoint
|
||||
- [ ] Extract SHA-256 hash from URL path
|
||||
- [ ] Require authorization with kind 24242 event validation
|
||||
- [ ] Validate `t` tag is set to "delete"
|
||||
- [ ] Verify at least one `x` tag matches the requested hash
|
||||
- [ ] Check authorization expiration
|
||||
- [ ] Verify event signature and structure
|
||||
- [ ] Check blob exists in database
|
||||
- [ ] Verify uploader_pubkey matches authorized pubkey (ownership check)
|
||||
- [ ] Remove blob file from filesystem
|
||||
- [ ] Remove blob metadata from database
|
||||
- [ ] Handle file deletion errors gracefully
|
||||
- [ ] Return appropriate success/error responses
|
||||
|
||||
### 2.7 Testing & Validation
|
||||
- [x] Test uploads without authentication
|
||||
- [x] Test uploads with valid nostr auth
|
||||
- [x] Test uploads with invalid auth
|
||||
@@ -195,7 +222,7 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
|
||||
- [x] HEAD requests return metadata from database
|
||||
- [x] Database stores blob information with proper schema
|
||||
|
||||
### Milestone 2: Full Upload Support (Phase 2 Complete)
|
||||
### Milestone 2: Full Upload Support (Phase 2 Pending)
|
||||
- [x] Basic upload functionality working (PUT requests accepted)
|
||||
- [x] SHA-256 hash calculation during upload
|
||||
- [x] File storage to blobs/ directory
|
||||
@@ -203,6 +230,8 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
|
||||
- [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)
|
||||
- [ ] List blobs endpoint implemented (GET /list/<pubkey>)
|
||||
- [ ] Delete blob endpoint implemented (DELETE /<sha256>)
|
||||
|
||||
### Milestone 3: Policy Compliance (Phase 3 Pending)
|
||||
- [ ] Upload requirements implemented
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Test blob content for Ginxsom Blossom server
|
||||
Timestamp: 2025-08-19T10:34:57-04:00
|
||||
Random data: 52bea9fe68b05eeeee258ae97cdcf8d9e7abbc13e67d61f681927250daef6e56
|
||||
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:34:52-04:00
|
||||
Random data: 340fc3549683d7c208ffa373d5932551f9b2e53cc1a5713b55c934403d9640a2
|
||||
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:34:56-04:00
|
||||
Random data: 52e5fee5b2f73c73ed388c9df9f74c58117b5f93982c5e6202951fd9fd90b626
|
||||
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:44:56-04:00
|
||||
Random data: 162b8c0930df1f600ddd936e99ecce0866ab82a436f545d2187ba56726f0df4f
|
||||
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.
@@ -102,6 +102,19 @@ http {
|
||||
}
|
||||
}
|
||||
|
||||
# List blobs endpoint - GET /list/<pubkey>
|
||||
location ~ "^/list/([a-f0-9]{64}).*$" {
|
||||
# Pass to FastCGI application for processing
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||
fastcgi_pass fastcgi_backend;
|
||||
|
||||
# Only allow GET method for list requests
|
||||
if ($request_method !~ ^(GET)$ ) {
|
||||
return 405;
|
||||
}
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
|
||||
BIN
db/ginxsom.db
BIN
db/ginxsom.db
Binary file not shown.
119
delete_test.sh
Executable file
119
delete_test.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
# delete_test.sh - Test script for DELETE /<sha256> endpoint
|
||||
# This script tests the blob deletion functionality
|
||||
|
||||
BASE_URL="http://localhost:9001"
|
||||
NOSTR_PRIVKEY="0000000000000000000000000000000000000000000000000000000000000001"
|
||||
NOSTR_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "=== Ginxsom Delete Blob Tests ==="
|
||||
echo
|
||||
|
||||
# Function to generate a Nostr event for delete authorization
|
||||
generate_delete_auth() {
|
||||
local sha256="$1"
|
||||
local content="$2"
|
||||
local created_at=$(date +%s)
|
||||
local expiration=$((created_at + 3600)) # 1 hour from now
|
||||
|
||||
# Note: This is a placeholder - in real implementation, you'd use nostr tools
|
||||
# to generate properly signed events. For now, we'll create the structure.
|
||||
cat << EOF
|
||||
{
|
||||
"id": "placeholder_id",
|
||||
"pubkey": "$NOSTR_PUBKEY",
|
||||
"kind": 24242,
|
||||
"content": "$content",
|
||||
"created_at": $created_at,
|
||||
"tags": [
|
||||
["t", "delete"],
|
||||
["x", "$sha256"],
|
||||
["expiration", "$expiration"]
|
||||
],
|
||||
"sig": "placeholder_signature"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Test 1: Delete without authorization (should fail)
|
||||
echo -e "${YELLOW}Test 1: DELETE without authorization${NC}"
|
||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X DELETE "$BASE_URL/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe")
|
||||
HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
if [ "$HTTP_STATUS" = "401" ]; then
|
||||
echo -e "${GREEN}✓ Correctly rejected unauthorized delete (401)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Expected 401, got $HTTP_STATUS${NC}"
|
||||
fi
|
||||
echo "Response: $BODY"
|
||||
echo
|
||||
|
||||
# Test 2: Delete with invalid authorization
|
||||
echo -e "${YELLOW}Test 2: DELETE with invalid authorization${NC}"
|
||||
INVALID_AUTH=$(echo '{"invalid": "event"}' | base64 -w 0)
|
||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X DELETE \
|
||||
-H "Authorization: Nostr $INVALID_AUTH" \
|
||||
"$BASE_URL/708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe")
|
||||
HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
if [ "$HTTP_STATUS" = "401" ]; then
|
||||
echo -e "${GREEN}✓ Correctly rejected invalid authorization (401)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Expected 401, got $HTTP_STATUS${NC}"
|
||||
fi
|
||||
echo "Response: $BODY"
|
||||
echo
|
||||
|
||||
# Test 3: Delete non-existent blob
|
||||
echo -e "${YELLOW}Test 3: DELETE non-existent blob${NC}"
|
||||
NONEXISTENT_HASH="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
DELETE_AUTH=$(generate_delete_auth "$NONEXISTENT_HASH" "Delete non-existent")
|
||||
AUTH_B64=$(echo "$DELETE_AUTH" | base64 -w 0)
|
||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X DELETE \
|
||||
-H "Authorization: Nostr $AUTH_B64" \
|
||||
"$BASE_URL/$NONEXISTENT_HASH")
|
||||
HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
if [ "$HTTP_STATUS" = "404" ]; then
|
||||
echo -e "${GREEN}✓ Correctly returned 404 for non-existent blob${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Expected 404, got $HTTP_STATUS${NC}"
|
||||
fi
|
||||
echo "Response: $BODY"
|
||||
echo
|
||||
|
||||
# Test 4: Delete with wrong pubkey (ownership check)
|
||||
echo -e "${YELLOW}Test 4: DELETE with wrong pubkey (ownership test)${NC}"
|
||||
TEST_HASH="708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe"
|
||||
DELETE_AUTH=$(generate_delete_auth "$TEST_HASH" "Delete with wrong pubkey")
|
||||
AUTH_B64=$(echo "$DELETE_AUTH" | base64 -w 0)
|
||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X DELETE \
|
||||
-H "Authorization: Nostr $AUTH_B64" \
|
||||
"$BASE_URL/$TEST_HASH")
|
||||
HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
echo "HTTP Status: $HTTP_STATUS"
|
||||
echo "Response: $BODY"
|
||||
echo
|
||||
|
||||
# Test 5: Valid delete (if implemented and authorized)
|
||||
echo -e "${YELLOW}Test 5: Valid DELETE request${NC}"
|
||||
echo "Note: This test requires a blob uploaded by the test pubkey"
|
||||
echo "and proper Nostr event signing (not implemented in this test script)"
|
||||
echo
|
||||
|
||||
echo "=== Delete Tests Complete ==="
|
||||
echo
|
||||
echo "Note: These tests use placeholder Nostr events."
|
||||
echo "For real testing, use proper Nostr signing tools to generate valid events."
|
||||
157
list_test.sh
Executable file
157
list_test.sh
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
|
||||
# list_test.sh - Test script for GET /list/<pubkey> endpoint
|
||||
# This script tests the blob listing functionality
|
||||
|
||||
BASE_URL="http://localhost:9001"
|
||||
NOSTR_PRIVKEY="0000000000000000000000000000000000000000000000000000000000000001"
|
||||
NOSTR_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "=== Ginxsom List Blobs Tests ==="
|
||||
echo
|
||||
|
||||
# Function to generate a Nostr event for list authorization
|
||||
generate_list_auth() {
|
||||
local content="$1"
|
||||
local created_at=$(date +%s)
|
||||
local expiration=$((created_at + 3600)) # 1 hour from now
|
||||
|
||||
# Note: This is a placeholder - in real implementation, you'd use nostr tools
|
||||
# to generate properly signed events. For now, we'll create the structure.
|
||||
cat << EOF
|
||||
{
|
||||
"id": "placeholder_id",
|
||||
"pubkey": "$NOSTR_PUBKEY",
|
||||
"kind": 24242,
|
||||
"content": "$content",
|
||||
"created_at": $created_at,
|
||||
"tags": [
|
||||
["t", "list"],
|
||||
["expiration", "$expiration"]
|
||||
],
|
||||
"sig": "placeholder_signature"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Test 1: List blobs without authorization (should work if optional auth)
|
||||
echo -e "${YELLOW}Test 1: GET /list/<pubkey> without authorization${NC}"
|
||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/list/$NOSTR_PUBKEY")
|
||||
HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
echo "HTTP Status: $HTTP_STATUS"
|
||||
echo "Response: $BODY"
|
||||
echo
|
||||
|
||||
# # Test 2: List blobs with authorization
|
||||
# echo -e "${YELLOW}Test 2: GET /list/<pubkey> with authorization${NC}"
|
||||
# LIST_AUTH=$(generate_list_auth "List Blobs")
|
||||
# AUTH_B64=$(echo "$LIST_AUTH" | base64 -w 0)
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
|
||||
# -H "Authorization: Nostr $AUTH_B64" \
|
||||
# "$BASE_URL/list/$NOSTR_PUBKEY")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 3: List blobs with since parameter
|
||||
# echo -e "${YELLOW}Test 3: GET /list/<pubkey> with since parameter${NC}"
|
||||
# SINCE_TIMESTAMP=$(($(date +%s) - 86400)) # 24 hours ago
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
|
||||
# "$BASE_URL/list/$NOSTR_PUBKEY?since=$SINCE_TIMESTAMP")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 4: List blobs with until parameter
|
||||
# echo -e "${YELLOW}Test 4: GET /list/<pubkey> with until parameter${NC}"
|
||||
# UNTIL_TIMESTAMP=$(date +%s) # now
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
|
||||
# "$BASE_URL/list/$NOSTR_PUBKEY?until=$UNTIL_TIMESTAMP")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 5: List blobs with both since and until parameters
|
||||
# echo -e "${YELLOW}Test 5: GET /list/<pubkey> with since and until parameters${NC}"
|
||||
# SINCE_TIMESTAMP=$(($(date +%s) - 86400)) # 24 hours ago
|
||||
# UNTIL_TIMESTAMP=$(date +%s) # now
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
|
||||
# "$BASE_URL/list/$NOSTR_PUBKEY?since=$SINCE_TIMESTAMP&until=$UNTIL_TIMESTAMP")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 6: List blobs for non-existent pubkey
|
||||
# echo -e "${YELLOW}Test 6: GET /list/<nonexistent_pubkey>${NC}"
|
||||
# FAKE_PUBKEY="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/list/$FAKE_PUBKEY")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# if [ "$HTTP_STATUS" = "200" ]; then
|
||||
# echo -e "${GREEN}✓ Correctly returned 200 with empty array${NC}"
|
||||
# else
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# fi
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 7: List blobs with invalid pubkey format
|
||||
# echo -e "${YELLOW}Test 7: GET /list/<invalid_pubkey_format>${NC}"
|
||||
# INVALID_PUBKEY="invalid_pubkey"
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/list/$INVALID_PUBKEY")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# if [ "$HTTP_STATUS" = "400" ]; then
|
||||
# echo -e "${GREEN}✓ Correctly returned 400 for invalid pubkey format${NC}"
|
||||
# else
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# fi
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# # Test 8: List blobs with invalid since/until parameters
|
||||
# echo -e "${YELLOW}Test 8: GET /list/<pubkey> with invalid timestamp parameters${NC}"
|
||||
# RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \
|
||||
# "$BASE_URL/list/$NOSTR_PUBKEY?since=invalid&until=invalid")
|
||||
# HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS" | cut -d: -f2)
|
||||
# BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
|
||||
|
||||
# echo "HTTP Status: $HTTP_STATUS"
|
||||
# echo "Response: $BODY"
|
||||
# echo
|
||||
|
||||
# echo "=== List Tests Complete ==="
|
||||
# echo
|
||||
# echo "Expected blob descriptor format:"
|
||||
# echo '{'
|
||||
# echo ' "url": "https://server.com/<sha256>.<ext>",'
|
||||
# echo ' "sha256": "<sha256>",'
|
||||
# echo ' "size": <bytes>,'
|
||||
# echo ' "type": "<mime_type>",'
|
||||
# echo ' "uploaded": <unix_timestamp>'
|
||||
# echo '}'
|
||||
# echo
|
||||
# echo "Note: These tests use placeholder Nostr events."
|
||||
# echo "For real testing, use proper Nostr signing tools to generate valid events."
|
||||
@@ -129,3 +129,23 @@
|
||||
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"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:53 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:53 -0400] "GET /545a2277dd4b7a66e320e12cdd92bf6fbbe13869b5bb5d665a03c83d453ba2de HTTP/1.1" 200 296 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:56 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:56 -0400] "GET /98681900bd97aabc4a7d2341bc52cc8d687e7c7b4dbd0893f6470242614d1100 HTTP/1.1" 200 296 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:57 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:34:57 -0400] "GET /22917078337a9df119979b8df2bbb59aafcc42161c50bd7881e68e27369f343c HTTP/1.1" 200 296 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:44:56 -0400] "PUT /upload HTTP/1.1" 200 323 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:44:56 -0400] "GET /b3bac1e07fa61f4668c0920b3493a571642e10c14e1325958eaac6d7e85e1fb1 HTTP/1.1" 200 296 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798?since=1755528942 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798?until=1755615342 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798?since=1755528942&until=1755615342 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/invalid_pubkey HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:55:42 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798?since=invalid&until=invalid HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:10:56:40 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:11:00:46 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:11:01:47 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 200 1984 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:11:02:33 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 200 1984 "-" "curl/8.15.0"
|
||||
|
||||
4675
logs/error.log
4675
logs/error.log
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
349770
|
||||
390406
|
||||
|
||||
@@ -71,11 +71,11 @@ void handle_upload_requirements_request(void);
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// HTTP response helpers
|
||||
void send_error_response(int status_code, const char* message);
|
||||
void send_error_response(int status_code, const char* error_type, const char* message, const char* details);
|
||||
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);
|
||||
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
313
src/main.c
313
src/main.c
@@ -23,6 +23,10 @@
|
||||
// Database path
|
||||
#define DB_PATH "db/ginxsom.db"
|
||||
|
||||
// Function declarations
|
||||
void send_error_response(int status_code, const char* error_type, const char* message, const char* details);
|
||||
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
|
||||
|
||||
// Blob metadata structure
|
||||
typedef struct {
|
||||
char sha256[MAX_SHA256_LEN];
|
||||
@@ -511,10 +515,229 @@ int authenticate_request(const char* auth_header, const char* method, const char
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
// Enhanced error response helper functions
|
||||
void send_error_response(int status_code, const char* error_type, const char* message, const char* details) {
|
||||
const char* status_text;
|
||||
switch (status_code) {
|
||||
case 400: status_text = "Bad Request"; break;
|
||||
case 401: status_text = "Unauthorized"; break;
|
||||
case 409: status_text = "Conflict"; break;
|
||||
case 413: status_text = "Payload Too Large"; break;
|
||||
case 500: status_text = "Internal Server Error"; break;
|
||||
default: status_text = "Error"; break;
|
||||
}
|
||||
|
||||
printf("Status: %d %s\r\n", status_code, status_text);
|
||||
printf("Content-Type: application/json\r\n\r\n");
|
||||
printf("{\n");
|
||||
printf(" \"error\": \"%s\",\n", error_type);
|
||||
printf(" \"message\": \"%s\"", message);
|
||||
if (details) {
|
||||
printf(",\n \"details\": \"%s\"", details);
|
||||
}
|
||||
printf("\n}\n");
|
||||
}
|
||||
|
||||
void log_request(const char* method, const char* uri, const char* auth_status, int status_code) {
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
char timestamp[64];
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
// For now, log to stdout - later can be configured to log files
|
||||
printf("LOG: [%s] %s %s - Auth: %s - Status: %d\r\n",
|
||||
timestamp, method ? method : "NULL", uri ? uri : "NULL",
|
||||
auth_status ? auth_status : "none", status_code);
|
||||
}
|
||||
|
||||
// Handle GET /list/<pubkey> requests
|
||||
void handle_list_request(const char* pubkey) {
|
||||
printf("DEBUG: handle_list_request called with pubkey=%s\r\n", pubkey ? pubkey : "NULL");
|
||||
|
||||
// Log the incoming request
|
||||
log_request("GET", "/list", "pending", 0);
|
||||
|
||||
// Validate pubkey format (64 hex characters)
|
||||
if (!pubkey || strlen(pubkey) != 64) {
|
||||
send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must be 64 hex characters");
|
||||
log_request("GET", "/list", "none", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate hex characters
|
||||
for (int i = 0; i < 64; i++) {
|
||||
char c = pubkey[i];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||
send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must contain only hex characters");
|
||||
log_request("GET", "/list", "none", 400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get query parameters for since/until filtering
|
||||
const char* query_string = getenv("QUERY_STRING");
|
||||
long since_timestamp = 0;
|
||||
long until_timestamp = 0;
|
||||
|
||||
if (query_string) {
|
||||
printf("DEBUG: Query string: %s\r\n", query_string);
|
||||
|
||||
// Parse since parameter
|
||||
const char* since_param = strstr(query_string, "since=");
|
||||
if (since_param) {
|
||||
since_timestamp = atol(since_param + 6);
|
||||
printf("DEBUG: Since timestamp: %ld\r\n", since_timestamp);
|
||||
}
|
||||
|
||||
// Parse until parameter
|
||||
const char* until_param = strstr(query_string, "until=");
|
||||
if (until_param) {
|
||||
until_timestamp = atol(until_param + 6);
|
||||
printf("DEBUG: Until timestamp: %ld\r\n", until_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for optional authorization
|
||||
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
||||
const char* auth_status = "none";
|
||||
|
||||
if (auth_header) {
|
||||
printf("DEBUG: Authorization header provided for list request\r\n");
|
||||
int auth_result = authenticate_request(auth_header, "list", NULL);
|
||||
if (auth_result != NOSTR_SUCCESS) {
|
||||
send_error_response(401, "authentication_failed", "Invalid or expired authentication",
|
||||
"The provided Nostr event is invalid, expired, or does not authorize this operation");
|
||||
log_request("GET", "/list", "failed", 401);
|
||||
return;
|
||||
}
|
||||
auth_status = "authenticated";
|
||||
}
|
||||
|
||||
// Query database for blobs uploaded by this pubkey
|
||||
sqlite3* db;
|
||||
sqlite3_stmt* stmt;
|
||||
int rc;
|
||||
|
||||
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if (rc) {
|
||||
printf("DEBUG: Database open failed: %s\r\n", sqlite3_errmsg(db));
|
||||
send_error_response(500, "database_error", "Failed to access database", "Internal server error");
|
||||
log_request("GET", "/list", auth_status, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build SQL query with optional timestamp filtering
|
||||
char sql[1024];
|
||||
if (since_timestamp > 0 && until_timestamp > 0) {
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at >= ? AND uploaded_at <= ? ORDER BY uploaded_at DESC");
|
||||
} else if (since_timestamp > 0) {
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at >= ? ORDER BY uploaded_at DESC");
|
||||
} else if (until_timestamp > 0) {
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at <= ? ORDER BY uploaded_at DESC");
|
||||
} else {
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? ORDER BY uploaded_at DESC");
|
||||
}
|
||||
|
||||
printf("DEBUG: SQL query: %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));
|
||||
sqlite3_close(db);
|
||||
send_error_response(500, "database_error", "Failed to prepare query", "Internal server error");
|
||||
log_request("GET", "/list", auth_status, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind parameters
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
int param_index = 2;
|
||||
|
||||
if (since_timestamp > 0) {
|
||||
sqlite3_bind_int64(stmt, param_index++, since_timestamp);
|
||||
}
|
||||
if (until_timestamp > 0) {
|
||||
sqlite3_bind_int64(stmt, param_index++, until_timestamp);
|
||||
}
|
||||
|
||||
// Start JSON response
|
||||
printf("Status: 200 OK\r\n");
|
||||
printf("Content-Type: application/json\r\n\r\n");
|
||||
printf("[\n");
|
||||
|
||||
int first_item = 1;
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
if (!first_item) {
|
||||
printf(",\n");
|
||||
}
|
||||
first_item = 0;
|
||||
|
||||
const char* sha256 = (const char*)sqlite3_column_text(stmt, 0);
|
||||
long size = sqlite3_column_int64(stmt, 1);
|
||||
const char* type = (const char*)sqlite3_column_text(stmt, 2);
|
||||
long uploaded_at = sqlite3_column_int64(stmt, 3);
|
||||
const char* filename = (const char*)sqlite3_column_text(stmt, 4);
|
||||
|
||||
// Determine file extension from MIME type
|
||||
const char* extension = "";
|
||||
if (strstr(type, "image/jpeg")) {
|
||||
extension = ".jpg";
|
||||
} else if (strstr(type, "image/webp")) {
|
||||
extension = ".webp";
|
||||
} else if (strstr(type, "image/png")) {
|
||||
extension = ".png";
|
||||
} else if (strstr(type, "image/gif")) {
|
||||
extension = ".gif";
|
||||
} else if (strstr(type, "video/mp4")) {
|
||||
extension = ".mp4";
|
||||
} else if (strstr(type, "video/webm")) {
|
||||
extension = ".webm";
|
||||
} else if (strstr(type, "audio/mpeg")) {
|
||||
extension = ".mp3";
|
||||
} else if (strstr(type, "audio/ogg")) {
|
||||
extension = ".ogg";
|
||||
} else if (strstr(type, "text/plain")) {
|
||||
extension = ".txt";
|
||||
} else {
|
||||
extension = ".bin";
|
||||
}
|
||||
|
||||
// Output blob descriptor JSON
|
||||
printf(" {\n");
|
||||
printf(" \"url\": \"http://localhost:9001/%s%s\",\n", sha256, extension);
|
||||
printf(" \"sha256\": \"%s\",\n", sha256);
|
||||
printf(" \"size\": %ld,\n", size);
|
||||
printf(" \"type\": \"%s\",\n", type);
|
||||
printf(" \"uploaded\": %ld", uploaded_at);
|
||||
|
||||
// Add optional filename if available
|
||||
if (filename && strlen(filename) > 0) {
|
||||
printf(",\n \"filename\": \"%s\"", filename);
|
||||
}
|
||||
|
||||
printf("\n }");
|
||||
}
|
||||
|
||||
printf("\n]\n");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
|
||||
printf("DEBUG: List request completed successfully\r\n");
|
||||
log_request("GET", "/list", auth_status, 200);
|
||||
}
|
||||
|
||||
// Handle PUT /upload requests
|
||||
void handle_upload_request(void) {
|
||||
printf("DEBUG: handle_upload_request called\r\n");
|
||||
|
||||
// Log the incoming request
|
||||
log_request("PUT", "/upload", "pending", 0);
|
||||
|
||||
// Get HTTP headers
|
||||
const char* content_type = getenv("CONTENT_TYPE");
|
||||
const char* content_length_str = getenv("CONTENT_LENGTH");
|
||||
@@ -524,24 +747,21 @@ void handle_upload_request(void) {
|
||||
|
||||
// Validate required headers
|
||||
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");
|
||||
send_error_response(400, "missing_header", "Content-Type header required", "The Content-Type header must be specified for file uploads");
|
||||
log_request("PUT", "/upload", "none", 400);
|
||||
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");
|
||||
send_error_response(400, "missing_header", "Content-Length header required", "The Content-Length header must be specified for file uploads");
|
||||
log_request("PUT", "/upload", "none", 400);
|
||||
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");
|
||||
send_error_response(413, "payload_too_large", "File size must be between 1 byte and 100MB", "Maximum allowed file size is 100MB");
|
||||
log_request("PUT", "/upload", "none", 413);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -549,54 +769,43 @@ void handle_upload_request(void) {
|
||||
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
|
||||
// For authenticated uploads, validate the authorization
|
||||
const char* uploader_pubkey = NULL;
|
||||
|
||||
if (auth_header) {
|
||||
printf("DEBUG: Authorization header present, length=%zu\r\n", strlen(auth_header));
|
||||
// Authenticate the request first (before processing file)
|
||||
int auth_result = authenticate_request(auth_header, "PUT", NULL);
|
||||
if (auth_result != NOSTR_SUCCESS) {
|
||||
send_error_response(401, "authentication_failed", "Invalid or expired authentication",
|
||||
"The provided Nostr event is invalid, expired, or does not authorize this operation");
|
||||
log_request("PUT", "/upload", "failed", 401);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse authorization header to get JSON
|
||||
// Extract pubkey for metadata storage
|
||||
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);
|
||||
}
|
||||
log_request("PUT", "/upload", "authenticated", 0);
|
||||
} else {
|
||||
printf("DEBUG: No authorization header provided\r\n");
|
||||
// No authentication provided - allow anonymous uploads
|
||||
log_request("PUT", "/upload", "anonymous", 0);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -633,6 +842,18 @@ void handle_upload_request(void) {
|
||||
nostr_bytes_to_hex(hash, 32, sha256_hex);
|
||||
printf("DEBUG: Calculated SHA-256: %s\r\n", sha256_hex);
|
||||
|
||||
// For authenticated uploads, verify hash matches the one in the authorization event
|
||||
if (auth_header) {
|
||||
int auth_result = authenticate_request(auth_header, "PUT", sha256_hex);
|
||||
if (auth_result != NOSTR_SUCCESS) {
|
||||
free(file_data);
|
||||
send_error_response(409, "hash_mismatch", "File hash does not match authorization",
|
||||
"The calculated SHA-256 hash of the uploaded file does not match the hash specified in the authorization event");
|
||||
log_request("PUT", "/upload", "hash_conflict", 409);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine file extension from Content-Type
|
||||
const char* extension = "";
|
||||
if (strstr(content_type, "image/jpeg")) {
|
||||
@@ -806,19 +1027,45 @@ int main(void) {
|
||||
printf("DEBUG: Extracted SHA256=%s\r\n", sha256 ? sha256 : "NULL");
|
||||
if (sha256) {
|
||||
handle_head_request(sha256);
|
||||
log_request("HEAD", request_uri, "none", 200); // Assuming success - could be enhanced to track actual status
|
||||
} else {
|
||||
printf("Status: 400 Bad Request\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Invalid SHA-256 hash in URI\n");
|
||||
log_request("HEAD", request_uri, "none", 400);
|
||||
}
|
||||
} else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/upload") == 0) {
|
||||
// Handle PUT /upload requests with authentication
|
||||
handle_upload_request();
|
||||
} else if (strcmp(request_method, "GET") == 0 && strncmp(request_uri, "/list/", 6) == 0) {
|
||||
// Handle GET /list/<pubkey> requests
|
||||
const char* pubkey = request_uri + 6; // Skip "/list/"
|
||||
|
||||
// Extract pubkey from URI (remove query string if present)
|
||||
static char pubkey_buffer[65];
|
||||
const char* query_start = strchr(pubkey, '?');
|
||||
size_t pubkey_len;
|
||||
|
||||
if (query_start) {
|
||||
pubkey_len = query_start - pubkey;
|
||||
} else {
|
||||
pubkey_len = strlen(pubkey);
|
||||
}
|
||||
|
||||
if (pubkey_len == 64) { // Valid pubkey length
|
||||
strncpy(pubkey_buffer, pubkey, 64);
|
||||
pubkey_buffer[64] = '\0';
|
||||
handle_list_request(pubkey_buffer);
|
||||
} else {
|
||||
send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must be 64 hex characters");
|
||||
log_request("GET", request_uri, "none", 400);
|
||||
}
|
||||
} else {
|
||||
// Other methods not implemented yet
|
||||
printf("Status: 501 Not Implemented\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Method %s not implemented\n", request_method);
|
||||
log_request(request_method, request_uri, "none", 501);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user