v0.1.38 - Update main.c and request_validator.c with latest changes
This commit is contained in:
Binary file not shown.
@@ -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>
|
||||
|
||||
277
src/main.c
277
src/main.c
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user