v0.1.38 - Update main.c and request_validator.c with latest changes

This commit is contained in:
Laan Tungir
2026-04-22 16:15:20 -04:00
parent 9491f5c373
commit 015094949e
4 changed files with 60 additions and 247 deletions

Binary file not shown.

View File

@@ -10,8 +10,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 0
#define VERSION_MINOR 1
#define VERSION_PATCH 37
#define VERSION "v0.1.37"
#define VERSION_PATCH 38
#define VERSION "v0.1.38"
#include <stddef.h>
#include <stdint.h>

View File

@@ -1599,256 +1599,43 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
// Handle PUT /upload requests
void handle_upload_request(void) {
// Log the incoming request
log_request("PUT", "/upload", "pending", 0);
// Get HTTP headers
// Legacy entry point: validate request, then reuse the unified upload handler.
const char *request_uri = getenv("REQUEST_URI");
const char *auth_header = getenv("HTTP_AUTHORIZATION");
const char *content_type = getenv("CONTENT_TYPE");
const char *content_length_str = getenv("CONTENT_LENGTH");
long content_length = content_length_str ? atol(content_length_str) : 0;
// Validate required headers
if (!content_type) {
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);
nostr_request_result_t result;
memset(&result, 0, sizeof(result));
nostr_unified_request_t request = {
.operation = "upload",
.auth_header = auth_header,
.event = NULL,
.resource_hash = NULL,
.mime_type = content_type,
.file_size = content_length,
.request_url = "ginxsom",
.challenge_id = NULL,
.nip42_enabled = 1,
.client_ip = getenv("REMOTE_ADDR"),
.app_context = NULL};
int validation_result = nostr_validate_unified_request(&request, &result);
if (validation_result != NOSTR_SUCCESS || !result.valid) {
const char *message =
result.reason[0] ? result.reason : "Authentication failed";
send_error_response(401, "authentication_failed", message,
"Authentication validation failed");
log_request("PUT", request_uri ? request_uri : "/upload", "auth_failed",
401);
nostr_request_result_free_file_data(&result);
return;
}
if (!content_length_str) {
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
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;
}
// Get Authorization header for authentication
const char *auth_header = getenv("HTTP_AUTHORIZATION");
// Store uploader pubkey for metadata (will be extracted during auth if
// provided)
const char *uploader_pubkey = NULL;
if (auth_header) {
log_request("PUT", "/upload", "auth_provided", 0);
} else {
log_request("PUT", "/upload", "anonymous", 0);
}
// 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("Memory allocation failed\n");
return;
}
size_t bytes_read = fread(file_data, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
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;
}
// 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);
fflush(stderr);
// Get dimensions from in-memory buffer before saving file
int width = 0, height = 0;
nip94_get_dimensions(file_data, content_length, content_type, &width,
&height);
// Determine file extension from Content-Type using centralized mapping
const char *extension = mime_to_extension(content_type);
// Save file to storage directory with SHA-256 + extension
char filepath[MAX_PATH_LEN];
// Construct path safely
size_t dir_len = strlen(g_storage_dir);
size_t sha_len = strlen(sha256_hex);
size_t ext_len = strlen(extension);
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
app_log(LOG_WARN, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("File path too long\n");
return;
}
// Build path manually to avoid compiler warnings
memcpy(filepath, g_storage_dir, dir_len);
filepath[dir_len] = '/';
memcpy(filepath + dir_len + 1, sha256_hex, sha_len);
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
filepath[total_len - 1] = '\0';
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);
// Set file permissions to 644 (owner read/write, group/others read) -
// standard for web files
if (chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
app_log(LOG_WARN, "WARNING: Failed to set file permissions for %s\r\n",
filepath);
// Continue anyway - this is not a fatal error
} else {
}
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;
}
// Extract filename from Content-Disposition header if present
const char *filename = NULL;
const char *content_disposition = getenv("HTTP_CONTENT_DISPOSITION");
if (content_disposition) {
// Look for filename= in Content-Disposition header
const char *filename_start = strstr(content_disposition, "filename=");
if (filename_start) {
filename_start += 9; // Skip "filename="
// Handle quoted filenames
if (*filename_start == '"') {
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;
if (filename_len < sizeof(filename_buffer)) {
strncpy(filename_buffer, filename_start, filename_len);
filename_buffer[filename_len] = '\0';
filename = filename_buffer;
} else {
}
} else {
}
} else {
// 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;
if (filename_len < sizeof(filename_buffer)) {
strncpy(filename_buffer, filename_start, filename_len);
filename_buffer[filename_len] = '\0';
filename = filename_buffer;
} else {
}
}
} else {
}
} else {
}
// 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
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;
}
// Get origin from config
char origin[256];
nip94_get_origin(origin, sizeof(origin));
// Build canonical blob URL
char blob_url[512];
nip94_build_blob_url(origin, sha256_hex, content_type, blob_url,
sizeof(blob_url));
// Return success response with blob descriptor
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"sha256\": \"%s\",\n", sha256_hex);
printf(" \"size\": %ld,\n", content_length);
printf(" \"type\": \"%s\",\n", content_type);
printf(" \"uploaded\": %ld,\n", uploaded_time);
printf(" \"url\": \"%s\"", blob_url);
// Add NIP-94 metadata if enabled
if (nip94_is_enabled()) {
printf(",\n");
nip94_emit_field(blob_url, content_type, sha256_hex, content_length, width,
height);
}
printf("\n}\n");
handle_upload_request_with_validation(&result);
nostr_request_result_free_file_data(&result);
}
// Handle PUT /upload requests with pre-validated file data from validator
@@ -1893,7 +1680,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// Extract uploader pubkey from validation result
const char *uploader_pubkey = NULL;
if (validation_result && strlen(validation_result->pubkey) == 64) {
if (validation_result && validation_result->valid && strlen(validation_result->pubkey) == 64) {
uploader_pubkey = validation_result->pubkey;
log_request("PUT", "/upload", "authenticated", 0);
} else if (auth_header) {

View File

@@ -303,6 +303,32 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "Authentication disabled");
// Even when auth is disabled, try to extract uploader pubkey from a provided
// auth header so uploads can still be attributed in blob metadata.
if (request->auth_header) {
char optional_event_json[4096];
int optional_parse = parse_authorization_header(
request->auth_header, optional_event_json, sizeof(optional_event_json));
if (optional_parse == NOSTR_SUCCESS) {
cJSON *optional_event = cJSON_Parse(optional_event_json);
if (optional_event) {
if (nostr_validate_event(optional_event) == NOSTR_SUCCESS) {
char optional_pubkey[65] = {0};
int optional_extract = extract_pubkey_from_event(
optional_event, optional_pubkey, sizeof(optional_pubkey));
if (optional_extract == NOSTR_SUCCESS && strlen(optional_pubkey) == 64) {
strncpy(result->pubkey, optional_pubkey, 64);
result->pubkey[64] = '\0';
strcpy(result->reason,
"Authentication disabled (pubkey extracted from auth header)");
}
}
cJSON_Delete(optional_event);
}
}
}
return NOSTR_SUCCESS;
}