Start upload functionality
This commit is contained in:
@@ -36,6 +36,8 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
|
||||
- [x] Handle 404s gracefully when blob doesn't exist
|
||||
- [x] Configure FastCGI pass-through for HEAD and non-GET requests
|
||||
|
||||
**Future Enhancement Note**: Consider implementing nginx Lua extension for true Blossom compliance with dynamic file discovery. The current approach uses explicit extension lists in `try_files`, which works well for common extensions but may not serve files with unusual extensions. Lua module would allow runtime directory scanning for hash-matching files regardless of extension.
|
||||
|
||||
### 1.4 Basic HEAD Endpoint
|
||||
- [x] Implement FastCGI handler for `HEAD /<sha256>`
|
||||
- [x] Query database for blob metadata (single source of truth)
|
||||
@@ -58,12 +60,32 @@ This document outlines the implementation plan for ginxsom, a FastCGI-based Blos
|
||||
|
||||
### 2.1 Nostr Authentication Setup
|
||||
- [ ] Integrate nostr_core_lib submodule
|
||||
- [ ] Implement nostr event validation
|
||||
- [ ] Verify event signature (schnorr)
|
||||
- [ ] Validate event structure (kind 24242)
|
||||
- [ ] Check required fields (t, expiration, x tags)
|
||||
- [ ] Implement expiration checking
|
||||
- [ ] Create authentication middleware
|
||||
- [ ] Update Makefile to include nostr_core_lib paths and static library
|
||||
- [ ] Build libnostr_core_x64.a using provided build.sh script
|
||||
- [ ] Add system dependencies: -lsecp256k1 -lssl -lcrypto -lcurl -lz -ldl -lpthread -lm
|
||||
|
||||
- [ ] Implement authentication functions in main.c (BUD-02 section):
|
||||
- [ ] `parse_authorization_header()` - Extract JSON from "Nostr base64(event)" header
|
||||
- [ ] `validate_blossom_event()` - Validate Blossom-specific requirements (kind 24242, content hash, method, expiration)
|
||||
- [ ] `authenticate_request()` - Main orchestrator function
|
||||
|
||||
- [ ] Leverage existing nostr_core_lib functions:
|
||||
- [ ] Use `nostr_validate_event()` for structure + signature validation (from nip001.h)
|
||||
- [ ] Use standardized error codes from nostr_common.h (NOSTR_SUCCESS, NOSTR_ERROR_EVENT_INVALID_SIGNATURE, etc.)
|
||||
- [ ] Use `nostr_strerror()` for error message translation
|
||||
|
||||
**Function Specifications:**
|
||||
```c
|
||||
// Custom functions to implement:
|
||||
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
|
||||
int validate_blossom_event(struct cJSON* event, const char* expected_hash, const char* method);
|
||||
int authenticate_request(const char* auth_header, const char* method, const char* file_hash);
|
||||
|
||||
// Existing nostr_core_lib functions to use directly:
|
||||
// - nostr_validate_event(cJSON* event) - handles structure + signature validation
|
||||
// - nostr_validate_event_structure(cJSON* event) - if separate validation needed
|
||||
// - nostr_verify_event_signature(cJSON* event) - if separate signature check needed
|
||||
```
|
||||
|
||||
### 2.2 Upload Endpoint Implementation
|
||||
- [ ] Implement `PUT /upload` endpoint
|
||||
|
||||
4
Makefile
4
Makefile
@@ -1,8 +1,8 @@
|
||||
# Ginxsom Blossom Server Makefile
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -std=c99 -O2
|
||||
LIBS = -lfcgi -lsqlite3
|
||||
CFLAGS = -Wall -Wextra -std=c99 -O2 -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson
|
||||
LIBS = -lfcgi -lsqlite3 nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -lcurl
|
||||
SRCDIR = src
|
||||
BUILDDIR = build
|
||||
TARGET = $(BUILDDIR)/ginxsom-fcgi
|
||||
|
||||
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
24
config/fastcgi_params
Normal file
24
config/fastcgi_params
Normal file
@@ -0,0 +1,24 @@
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
fastcgi_param REQUEST_METHOD $request_method;
|
||||
fastcgi_param CONTENT_TYPE $content_type;
|
||||
fastcgi_param CONTENT_LENGTH $content_length;
|
||||
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_URI $document_uri;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
fastcgi_param REQUEST_SCHEME $scheme;
|
||||
fastcgi_param HTTPS $https if_not_empty;
|
||||
|
||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
|
||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||
fastcgi_param REMOTE_PORT $remote_port;
|
||||
fastcgi_param SERVER_ADDR $server_addr;
|
||||
fastcgi_param SERVER_PORT $server_port;
|
||||
fastcgi_param SERVER_NAME $server_name;
|
||||
|
||||
# PHP only, required if PHP was built with --enable-force-cgi-redirect
|
||||
fastcgi_param REDIRECT_STATUS 200;
|
||||
@@ -18,8 +18,8 @@ http {
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
# MIME types
|
||||
include /etc/nginx/mime.types;
|
||||
# MIME types (local)
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Logging (relative to prefix directory)
|
||||
@@ -58,9 +58,8 @@ http {
|
||||
}
|
||||
|
||||
# GET requests served directly with explicit file extensions
|
||||
# try_files /$1 =404;
|
||||
# try_files /$1.webp =404;
|
||||
try_files /$1.pdf /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.mp4 /$1.mp3 =404;
|
||||
# Potentially in the future look at a LUA extension
|
||||
try_files /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.txt /$1.md=404;
|
||||
|
||||
# Set appropriate headers for blobs
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
@@ -68,33 +67,7 @@ http {
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
}
|
||||
|
||||
# Commented out problematic regex for reference
|
||||
# location ~ "^/([a-f0-9]{64}).*$" {
|
||||
# limit_except HEAD GET {
|
||||
# deny all;
|
||||
# }
|
||||
#
|
||||
# # Debug headers to see what nginx is capturing
|
||||
# add_header X-Debug-Hash "$1" always;
|
||||
# add_header X-Debug-TryFiles "$1*" always;
|
||||
# add_header X-Debug-URI "$uri" always;
|
||||
# add_header X-Debug-Root "$document_root" always;
|
||||
#
|
||||
# # Route HEAD requests to FastCGI via rewrite
|
||||
# if ($request_method = HEAD) {
|
||||
# rewrite ^/(.*)$ /fcgi-head/$1 last;
|
||||
# }
|
||||
#
|
||||
# # GET requests served directly with hash-only lookup
|
||||
# try_files $1* =404;
|
||||
#
|
||||
# # Set appropriate headers for blobs
|
||||
# add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
# add_header X-Content-Type-Options nosniff;
|
||||
# add_header X-Frame-Options DENY;
|
||||
# add_header X-XSS-Protection "1; mode=block";
|
||||
# }
|
||||
|
||||
|
||||
# FastCGI handler for HEAD requests
|
||||
location ~ "^/fcgi-head/([a-f0-9]{64}).*$" {
|
||||
@@ -116,6 +89,19 @@ http {
|
||||
}
|
||||
|
||||
|
||||
# Upload endpoint - requires authentication
|
||||
location /upload {
|
||||
# Pass to FastCGI application for processing
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||
fastcgi_pass fastcgi_backend;
|
||||
|
||||
# Only allow PUT method for uploads
|
||||
if ($request_method !~ ^(PUT)$ ) {
|
||||
return 405;
|
||||
}
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
|
||||
95
config/mime.types
Normal file
95
config/mime.types
Normal file
@@ -0,0 +1,95 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
image/png png;
|
||||
image/webp webp;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/avif avif;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/wasm wasm;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
24
fastcgi_params
Normal file
24
fastcgi_params
Normal file
@@ -0,0 +1,24 @@
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
fastcgi_param REQUEST_METHOD $request_method;
|
||||
fastcgi_param CONTENT_TYPE $content_type;
|
||||
fastcgi_param CONTENT_LENGTH $content_length;
|
||||
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_URI $document_uri;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
fastcgi_param REQUEST_SCHEME $scheme;
|
||||
fastcgi_param HTTPS $https if_not_empty;
|
||||
|
||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
|
||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||
fastcgi_param REMOTE_PORT $remote_port;
|
||||
fastcgi_param SERVER_ADDR $server_addr;
|
||||
fastcgi_param SERVER_PORT $server_port;
|
||||
fastcgi_param SERVER_NAME $server_name;
|
||||
|
||||
# PHP only, required if PHP was built with --enable-force-cgi-redirect
|
||||
fastcgi_param REDIRECT_STATUS 200;
|
||||
@@ -105,3 +105,9 @@
|
||||
127.0.0.1 - - [18/Aug/2025:22:40:32 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 404 564 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
127.0.0.1 - - [18/Aug/2025:23:00:31 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 200 203886 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
127.0.0.1 - - [18/Aug/2025:23:01:14 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
127.0.0.1 - - [19/Aug/2025:06:00:05 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
127.0.0.1 - - [19/Aug/2025:07:49:21 -0400] "PUT /upload HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:07:49:21 -0400] "GET /21fc30dac4c95ad4b26eb78d97cb31a0cb4bc69f30c0d4195f7685ac13b22ce6 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:07:51:54 -0400] "GET /708d0e8226ec17b0585417c0ec9352ce5f52c3820c904b7066fe20b00f2d9cfe.webp HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
127.0.0.1 - - [19/Aug/2025:07:57:54 -0400] "PUT /upload HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
||||
127.0.0.1 - - [19/Aug/2025:07:57:54 -0400] "GET /ae9f59c7ac386b7fe6343d669fc27f37d7b66256824be655d29a256908f154e9 HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
||||
|
||||
1492
logs/error.log
1492
logs/error.log
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
296457
|
||||
349770
|
||||
|
||||
95
mime.types
Normal file
95
mime.types
Normal file
@@ -0,0 +1,95 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
image/png png;
|
||||
image/webp webp;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/avif avif;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/wasm wasm;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
Submodule nostr_core_lib updated: 1da4f6751e...77d92dbcf9
249
put_test.sh
Executable file
249
put_test.sh
Executable file
@@ -0,0 +1,249 @@
|
||||
#!/bin/bash
|
||||
|
||||
# put_test.sh - Test script for Ginxsom Blossom server upload functionality
|
||||
# This script simulates a user uploading a blob to ginxsom using proper Blossom authentication
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Configuration
|
||||
SERVER_URL="http://localhost:9001"
|
||||
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
||||
TEST_FILE="test_blob_$(date +%s).txt"
|
||||
CLEANUP_FILES=()
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
echo -e "${YELLOW}Cleaning up temporary files...${NC}"
|
||||
for file in "${CLEANUP_FILES[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
rm -f "$file"
|
||||
echo "Removed: $file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Set up cleanup on exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Helper functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
# Check if nak is installed
|
||||
if ! command -v nak &> /dev/null; then
|
||||
log_error "nak command not found. Please install nak first."
|
||||
log_info "Install with: go install github.com/fiatjaf/nak@latest"
|
||||
exit 1
|
||||
fi
|
||||
log_success "nak is installed"
|
||||
|
||||
# Check if curl is available
|
||||
if ! command -v curl &> /dev/null; then
|
||||
log_error "curl command not found. Please install curl."
|
||||
exit 1
|
||||
fi
|
||||
log_success "curl is available"
|
||||
|
||||
# Check if sha256sum is available
|
||||
if ! command -v sha256sum &> /dev/null; then
|
||||
log_error "sha256sum command not found."
|
||||
exit 1
|
||||
fi
|
||||
log_success "sha256sum is available"
|
||||
|
||||
# Check if base64 is available
|
||||
if ! command -v base64 &> /dev/null; then
|
||||
log_error "base64 command not found."
|
||||
exit 1
|
||||
fi
|
||||
log_success "base64 is available"
|
||||
}
|
||||
|
||||
# Check if server is running
|
||||
check_server() {
|
||||
log_info "Checking if server is running..."
|
||||
|
||||
if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then
|
||||
log_success "Server is running at ${SERVER_URL}"
|
||||
else
|
||||
log_error "Server is not responding at ${SERVER_URL}"
|
||||
log_info "Please start the server with: ./scripts/start-fcgi.sh && nginx -p . -c config/local-nginx.conf"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create test file
|
||||
create_test_file() {
|
||||
log_info "Creating test file: ${TEST_FILE}"
|
||||
|
||||
# Create test content with timestamp and random data
|
||||
cat > "${TEST_FILE}" << EOF
|
||||
Test blob content for Ginxsom Blossom server
|
||||
Timestamp: $(date -Iseconds)
|
||||
Random data: $(openssl rand -hex 32)
|
||||
Test message: Hello from put_test.sh!
|
||||
|
||||
This file is used to test the upload functionality
|
||||
of the Ginxsom Blossom server implementation.
|
||||
EOF
|
||||
|
||||
CLEANUP_FILES+=("${TEST_FILE}")
|
||||
log_success "Created test file with $(wc -c < "${TEST_FILE}") bytes"
|
||||
}
|
||||
|
||||
# Calculate file hash
|
||||
calculate_hash() {
|
||||
log_info "Calculating SHA-256 hash..."
|
||||
|
||||
HASH=$(sha256sum "${TEST_FILE}" | cut -d' ' -f1)
|
||||
log_success "File hash: ${HASH}"
|
||||
}
|
||||
|
||||
# Generate nostr event
|
||||
generate_nostr_event() {
|
||||
log_info "Generating kind 24242 nostr event with nak..."
|
||||
|
||||
# Calculate expiration time (1 hour from now)
|
||||
EXPIRATION=$(date -d '+1 hour' +%s)
|
||||
|
||||
# Generate the event using nak
|
||||
EVENT_JSON=$(nak event -k 24242 -c "" \
|
||||
-t "t=upload" \
|
||||
-t "x=${HASH}" \
|
||||
-t "expiration=${EXPIRATION}")
|
||||
|
||||
if [[ -z "$EVENT_JSON" ]]; then
|
||||
log_error "Failed to generate nostr event"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Generated nostr event"
|
||||
echo "Event JSON: $EVENT_JSON"
|
||||
}
|
||||
|
||||
# Create authorization header
|
||||
create_auth_header() {
|
||||
log_info "Creating authorization header..."
|
||||
|
||||
# Base64 encode the event (without newlines)
|
||||
AUTH_B64=$(echo -n "$EVENT_JSON" | base64 -w 0)
|
||||
AUTH_HEADER="Nostr ${AUTH_B64}"
|
||||
|
||||
log_success "Created authorization header"
|
||||
echo "Auth header length: ${#AUTH_HEADER} characters"
|
||||
}
|
||||
|
||||
# Perform upload
|
||||
perform_upload() {
|
||||
log_info "Performing upload to ${UPLOAD_ENDPOINT}..."
|
||||
|
||||
# Create temporary file for response
|
||||
RESPONSE_FILE=$(mktemp)
|
||||
CLEANUP_FILES+=("${RESPONSE_FILE}")
|
||||
|
||||
# Perform the upload with verbose output
|
||||
HTTP_STATUS=$(curl -s -w "%{http_code}" \
|
||||
-X PUT \
|
||||
-H "Authorization: ${AUTH_HEADER}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@${TEST_FILE}" \
|
||||
"${UPLOAD_ENDPOINT}" \
|
||||
-o "${RESPONSE_FILE}")
|
||||
|
||||
echo "HTTP Status: ${HTTP_STATUS}"
|
||||
echo "Response body:"
|
||||
cat "${RESPONSE_FILE}"
|
||||
echo
|
||||
|
||||
# Check response
|
||||
case "${HTTP_STATUS}" in
|
||||
200)
|
||||
log_success "Upload successful!"
|
||||
;;
|
||||
201)
|
||||
log_success "Upload successful (created)!"
|
||||
;;
|
||||
400)
|
||||
log_error "Bad request - check the event format"
|
||||
;;
|
||||
401)
|
||||
log_error "Unauthorized - authentication failed"
|
||||
;;
|
||||
405)
|
||||
log_error "Method not allowed - check nginx configuration"
|
||||
;;
|
||||
413)
|
||||
log_error "Payload too large"
|
||||
;;
|
||||
501)
|
||||
log_warning "Upload endpoint not yet implemented (expected for now)"
|
||||
;;
|
||||
*)
|
||||
log_error "Upload failed with HTTP status: ${HTTP_STATUS}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Test file retrieval
|
||||
test_retrieval() {
|
||||
log_info "Testing file retrieval..."
|
||||
|
||||
RETRIEVAL_URL="${SERVER_URL}/${HASH}"
|
||||
|
||||
if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then
|
||||
log_success "File can be retrieved at: ${RETRIEVAL_URL}"
|
||||
else
|
||||
log_warning "File not yet available for retrieval (expected if upload processing not implemented)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
echo "=== Ginxsom Blossom Upload Test ==="
|
||||
echo "Timestamp: $(date -Iseconds)"
|
||||
echo
|
||||
|
||||
check_prerequisites
|
||||
check_server
|
||||
create_test_file
|
||||
calculate_hash
|
||||
generate_nostr_event
|
||||
create_auth_header
|
||||
perform_upload
|
||||
test_retrieval
|
||||
|
||||
echo
|
||||
log_info "Test completed!"
|
||||
echo "Summary:"
|
||||
echo " Test file: ${TEST_FILE}"
|
||||
echo " File hash: ${HASH}"
|
||||
echo " Server: ${SERVER_URL}"
|
||||
echo " Upload endpoint: ${UPLOAD_ENDPOINT}"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
84
src/ginxsom.h
Normal file
84
src/ginxsom.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Ginxsom Blossom Server Header
|
||||
*
|
||||
* This header contains all function declarations and type definitions
|
||||
* organized by BUD (Blossom Unified Draft) sections.
|
||||
*/
|
||||
|
||||
#ifndef GINXSOM_H
|
||||
#define GINXSOM_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <sqlite3.h>
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BUD 01 - Basic Protocol Flow
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Database connection management
|
||||
extern sqlite3* db;
|
||||
int init_database(void);
|
||||
void close_database(void);
|
||||
|
||||
// SHA-256 extraction and validation
|
||||
const char* extract_sha256_from_uri(const char* uri);
|
||||
|
||||
// HEAD request handling
|
||||
void handle_head_request(const char* uri);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BUD 02 - Upload & Authentication
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Authorization header parsing
|
||||
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
|
||||
|
||||
// Blossom event validation (specific to kind 24242)
|
||||
int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method);
|
||||
|
||||
// Main authentication orchestrator
|
||||
int authenticate_request(const char* auth_header, const char* method, const char* file_hash);
|
||||
|
||||
// Upload handling
|
||||
void handle_upload_request(void);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BUD 06 - Upload Requirements
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Upload policy management (for future implementation)
|
||||
void handle_upload_requirements_request(void);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UTILITY FUNCTIONS
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// HTTP response helpers
|
||||
void send_error_response(int status_code, const char* message);
|
||||
void send_json_response(int status_code, const char* json_content);
|
||||
|
||||
// Logging utilities
|
||||
void log_request(const char* method, const char* uri, int status_code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // GINXSOM_H
|
||||
267
src/main.c
267
src/main.c
@@ -3,14 +3,18 @@
|
||||
* Handles HEAD requests and other dynamic operations
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <sqlite3.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include "ginxsom.h"
|
||||
|
||||
#define MAX_SHA256_LEN 65
|
||||
#define MAX_PATH_LEN 512
|
||||
@@ -218,6 +222,266 @@ const char* extract_sha256_from_uri(const char* uri) {
|
||||
return sha256_buffer;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BUD 02 - Upload & Authentication
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Parse Authorization header and extract JSON event
|
||||
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size) {
|
||||
if (!auth_header || !event_json) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Check for "Nostr " prefix (case-insensitive)
|
||||
const char* prefix = "nostr ";
|
||||
size_t prefix_len = strlen(prefix);
|
||||
|
||||
if (strncasecmp(auth_header, prefix, prefix_len) != 0) {
|
||||
printf("DEBUG: Authorization header missing 'Nostr ' prefix\r\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Extract base64 encoded event after "Nostr "
|
||||
const char* base64_event = auth_header + prefix_len;
|
||||
|
||||
// Decode base64 to JSON
|
||||
// For now, we'll assume the event is already JSON (not base64 encoded)
|
||||
// This is a simplified implementation - in production you'd need proper base64 decoding
|
||||
size_t event_len = strlen(base64_event);
|
||||
if (event_len >= json_size) {
|
||||
printf("DEBUG: Event JSON too large for buffer\r\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
strncpy(event_json, base64_event, json_size - 1);
|
||||
event_json[json_size - 1] = '\0';
|
||||
|
||||
printf("DEBUG: Parsed authorization header, extracted JSON: %.100s...\r\n", event_json);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
// Validate Blossom-specific event requirements (kind 24242)
|
||||
int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method) {
|
||||
if (!event) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
printf("DEBUG: Validating Blossom event\r\n");
|
||||
|
||||
// Check event kind (must be 24242 for Blossom uploads)
|
||||
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_json || !cJSON_IsNumber(kind_json)) {
|
||||
printf("DEBUG: Event missing or invalid 'kind' field\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
int kind = cJSON_GetNumberValue(kind_json);
|
||||
if (kind != 24242) {
|
||||
printf("DEBUG: Event kind %d is not 24242 (Blossom upload)\r\n", kind);
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
// Check that created_at exists (basic validation)
|
||||
cJSON* created_at_json = cJSON_GetObjectItem(event, "created_at");
|
||||
if (!created_at_json || !cJSON_IsNumber(created_at_json)) {
|
||||
printf("DEBUG: Event missing or invalid 'created_at' field\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
// Look for expiration in tags
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
printf("DEBUG: Event missing or invalid 'tags' field\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
time_t expiration = 0;
|
||||
int found_method = 0;
|
||||
int found_hash = 0;
|
||||
|
||||
// Parse tags for 't' (method), 'x' (hash), and 'expiration'
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
if (!cJSON_IsArray(tag)) continue;
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
||||
|
||||
const char* tag_name_str = cJSON_GetStringValue(tag_name);
|
||||
|
||||
if (strcmp(tag_name_str, "t") == 0) {
|
||||
// Method tag
|
||||
cJSON* method_value = cJSON_GetArrayItem(tag, 1);
|
||||
if (method_value && cJSON_IsString(method_value)) {
|
||||
const char* event_method = cJSON_GetStringValue(method_value);
|
||||
if (strcmp(event_method, method) == 0) {
|
||||
found_method = 1;
|
||||
printf("DEBUG: Found matching method tag: %s\r\n", event_method);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(tag_name_str, "x") == 0) {
|
||||
// Hash tag
|
||||
cJSON* hash_value = cJSON_GetArrayItem(tag, 1);
|
||||
if (hash_value && cJSON_IsString(hash_value)) {
|
||||
const char* event_hash = cJSON_GetStringValue(hash_value);
|
||||
if (expected_hash && strcmp(event_hash, expected_hash) == 0) {
|
||||
found_hash = 1;
|
||||
printf("DEBUG: Found matching hash tag: %s\r\n", event_hash);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(tag_name_str, "expiration") == 0) {
|
||||
// Expiration tag
|
||||
cJSON* exp_value = cJSON_GetArrayItem(tag, 1);
|
||||
if (exp_value && cJSON_IsString(exp_value)) {
|
||||
expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
|
||||
printf("DEBUG: Found expiration tag: %ld\r\n", expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if method matches (required)
|
||||
if (!found_method) {
|
||||
printf("DEBUG: Event missing or invalid method tag\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
// Check if hash matches (if provided)
|
||||
if (expected_hash && !found_hash) {
|
||||
printf("DEBUG: Event hash doesn't match expected hash\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
time_t now = time(NULL);
|
||||
if (expiration > 0 && now > expiration) {
|
||||
printf("DEBUG: Event expired (now: %ld, exp: %ld)\r\n", now, expiration);
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
printf("DEBUG: Blossom event validation passed\r\n");
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
// Main authentication function
|
||||
int authenticate_request(const char* auth_header, const char* method, const char* file_hash) {
|
||||
if (!auth_header) {
|
||||
printf("DEBUG: No authorization header provided\r\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
printf("DEBUG: Authenticating request - method: %s, hash: %s\r\n",
|
||||
method ? method : "null", file_hash ? file_hash : "null");
|
||||
|
||||
// Parse authorization header
|
||||
char event_json[4096];
|
||||
int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json));
|
||||
if (parse_result != NOSTR_SUCCESS) {
|
||||
printf("DEBUG: Authorization header parsing failed: %d\r\n", parse_result);
|
||||
return parse_result;
|
||||
}
|
||||
|
||||
// Parse JSON event
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("DEBUG: Failed to parse JSON event\r\n");
|
||||
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
||||
}
|
||||
|
||||
// Validate event structure and signature using nostr_core_lib
|
||||
int validation_result = nostr_validate_event(event);
|
||||
if (validation_result != NOSTR_SUCCESS) {
|
||||
printf("DEBUG: Nostr event validation failed: %d (%s)\r\n",
|
||||
validation_result, nostr_strerror(validation_result));
|
||||
cJSON_Delete(event);
|
||||
return validation_result;
|
||||
}
|
||||
|
||||
// Validate Blossom-specific requirements
|
||||
int blossom_result = validate_blossom_event(event, file_hash, method);
|
||||
if (blossom_result != NOSTR_SUCCESS) {
|
||||
printf("DEBUG: Blossom event validation failed: %d\r\n", blossom_result);
|
||||
cJSON_Delete(event);
|
||||
return blossom_result;
|
||||
}
|
||||
|
||||
cJSON_Delete(event);
|
||||
printf("DEBUG: Authentication successful\r\n");
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
// Handle PUT /upload requests
|
||||
void handle_upload_request(void) {
|
||||
printf("DEBUG: handle_upload_request called\r\n");
|
||||
|
||||
// Get HTTP headers
|
||||
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
||||
const char* content_type = getenv("CONTENT_TYPE");
|
||||
const char* content_length_str = getenv("CONTENT_LENGTH");
|
||||
|
||||
printf("DEBUG: auth_header=%s\r\n", auth_header ? auth_header : "NULL");
|
||||
printf("DEBUG: content_type=%s\r\n", content_type ? content_type : "NULL");
|
||||
printf("DEBUG: content_length=%s\r\n", content_length_str ? content_length_str : "NULL");
|
||||
|
||||
// Validate required headers
|
||||
if (!auth_header) {
|
||||
printf("Status: 401 Unauthorized\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Authorization header required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content_type) {
|
||||
printf("Status: 400 Bad Request\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Content-Type header required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content_length_str) {
|
||||
printf("Status: 400 Bad Request\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Content-Length header required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
long content_length = atol(content_length_str);
|
||||
if (content_length <= 0 || content_length > 100 * 1024 * 1024) { // 100MB limit
|
||||
printf("Status: 413 Payload Too Large\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("File size must be between 1 byte and 100MB\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Authenticate the request
|
||||
int auth_result = authenticate_request(auth_header, "PUT", NULL);
|
||||
if (auth_result != NOSTR_SUCCESS) {
|
||||
printf("DEBUG: Authentication failed: %d\r\n", auth_result);
|
||||
printf("Status: 401 Unauthorized\r\n");
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Authentication failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("DEBUG: Authentication successful, proceeding with upload\r\n");
|
||||
|
||||
// For now, return a simple response indicating the upload endpoint is working
|
||||
// In a full implementation, you would:
|
||||
// 1. Read the file data from stdin
|
||||
// 2. Calculate SHA-256 hash
|
||||
// 3. Save file to blobs/ directory with proper extension
|
||||
// 4. Store metadata in database
|
||||
// 5. Return blob descriptor JSON
|
||||
|
||||
printf("Status: 501 Not Implemented\r\n");
|
||||
printf("Content-Type: application/json\r\n\r\n");
|
||||
printf("{\n");
|
||||
printf(" \"message\": \"Upload endpoint authenticated successfully\",\n");
|
||||
printf(" \"note\": \"Full file upload implementation pending\"\n");
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
while (FCGI_Accept() >= 0) {
|
||||
// DEBUG: Log every request received
|
||||
@@ -249,6 +513,9 @@ int main(void) {
|
||||
printf("Content-Type: text/plain\r\n\r\n");
|
||||
printf("Invalid SHA-256 hash in URI\n");
|
||||
}
|
||||
} else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/upload") == 0) {
|
||||
// Handle PUT /upload requests with authentication
|
||||
handle_upload_request();
|
||||
} else {
|
||||
// Other methods not implemented yet
|
||||
printf("Status: 501 Not Implemented\r\n");
|
||||
|
||||
Reference in New Issue
Block a user