Refactored code by breaking the main.c up into BUD files.
This commit is contained in:
265
src/bud06.c
Normal file
265
src/bud06.c
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* BUD-06 Upload Requirements (Pre-flight Validation)
|
||||
* Handles HEAD /upload requests for upload requirement validation
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include "ginxsom.h"
|
||||
#include "../nostr_core_lib/nostr_core/request_validator.h"
|
||||
|
||||
// BUD-06 X-Reason header constants
|
||||
#define XREASON_MISSING_SHA256 "Missing required X-SHA-256 header"
|
||||
#define XREASON_INVALID_SHA256 "X-SHA-256 must be 64 hex characters"
|
||||
#define XREASON_MISSING_LENGTH "Missing required X-Content-Length header"
|
||||
#define XREASON_INVALID_LENGTH "X-Content-Length must be a positive integer"
|
||||
#define XREASON_FILE_TOO_LARGE "File size exceeds maximum allowed (100MB)"
|
||||
#define XREASON_ZERO_LENGTH "File size cannot be zero"
|
||||
#define XREASON_BLOB_EXISTS "Blob with this hash already exists"
|
||||
#define XREASON_UNSUPPORTED_TYPE "Content type not supported by server policy"
|
||||
#define XREASON_AUTH_REQUIRED "Authorization required for upload"
|
||||
#define XREASON_AUTH_INVALID "Invalid or expired authorization"
|
||||
|
||||
// Enhanced error response with X-Reason header for BUD-06
|
||||
void send_upload_error_response(int status_code, const char* error_type,
|
||||
const char* message, const char* x_reason) {
|
||||
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 411: status_text = "Length Required"; break;
|
||||
case 413: status_text = "Content Too Large"; break;
|
||||
case 415: status_text = "Unsupported Media Type"; 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");
|
||||
if (x_reason) {
|
||||
printf("X-Reason: %s\r\n", x_reason);
|
||||
}
|
||||
printf("\r\n");
|
||||
printf("{\n");
|
||||
printf(" \"error\": \"%s\",\n", error_type);
|
||||
printf(" \"message\": \"%s\"", message);
|
||||
if (x_reason) {
|
||||
printf(",\n \"x_reason\": \"%s\"", x_reason);
|
||||
}
|
||||
printf("\n}\n");
|
||||
}
|
||||
|
||||
// Success response for validated upload requirements
|
||||
void send_upload_success_response(const char* sha256, const char* content_type, long content_length) {
|
||||
printf("Status: 200 OK\r\n");
|
||||
printf("Content-Type: application/json\r\n");
|
||||
printf("X-Upload-Status: Ready\r\n");
|
||||
printf("\r\n");
|
||||
printf("{\n");
|
||||
printf(" \"message\": \"Upload requirements validated\",\n");
|
||||
printf(" \"sha256\": \"%s\",\n", sha256);
|
||||
printf(" \"content_type\": \"%s\",\n", content_type);
|
||||
printf(" \"content_length\": %ld\n", content_length);
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
// Validate SHA-256 format (64 hex characters)
|
||||
int validate_sha256_format(const char* sha256) {
|
||||
if (!sha256 || strlen(sha256) != 64) {
|
||||
return 0; // Invalid format
|
||||
}
|
||||
|
||||
// Check that all characters are hex
|
||||
for (int i = 0; i < 64; i++) {
|
||||
char c = sha256[i];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||
return 0; // Invalid hex character
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // Valid format
|
||||
}
|
||||
|
||||
// Parse and validate X-Content-Length header
|
||||
int validate_content_length(const char* content_length_str, long* parsed_length) {
|
||||
if (!content_length_str || !parsed_length) {
|
||||
return 0; // Invalid input
|
||||
}
|
||||
|
||||
char* endptr;
|
||||
long length = strtol(content_length_str, &endptr, 10);
|
||||
|
||||
// Always set parsed_length so caller can check the actual value
|
||||
*parsed_length = length;
|
||||
|
||||
// Check if conversion was successful and no trailing characters
|
||||
if (*endptr != '\0') {
|
||||
return 0; // Invalid number format
|
||||
}
|
||||
|
||||
// Check for valid size range
|
||||
if (length <= 0) {
|
||||
return 0; // Zero or negative size not allowed
|
||||
}
|
||||
|
||||
if (length > 100 * 1024 * 1024) { // 100MB limit
|
||||
return -1; // File too large
|
||||
}
|
||||
|
||||
return 1; // Valid length
|
||||
}
|
||||
|
||||
// Check if blob already exists in database
|
||||
int check_blob_exists(const char* sha256) {
|
||||
if (!sha256) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
blob_metadata_t metadata = {0};
|
||||
return get_blob_metadata(sha256, &metadata);
|
||||
}
|
||||
|
||||
// Validate upload headers and extract values
|
||||
int validate_upload_headers(const char** sha256, const char** content_type,
|
||||
long* content_length, char* error_reason, size_t reason_size) {
|
||||
// Get X-SHA-256 header
|
||||
const char* sha256_header = getenv("HTTP_X_SHA_256");
|
||||
if (!sha256_header) {
|
||||
strncpy(error_reason, XREASON_MISSING_SHA256, reason_size - 1);
|
||||
error_reason[reason_size - 1] = '\0';
|
||||
return 400; // Bad Request
|
||||
}
|
||||
|
||||
// Validate SHA-256 format
|
||||
if (!validate_sha256_format(sha256_header)) {
|
||||
strncpy(error_reason, XREASON_INVALID_SHA256, reason_size - 1);
|
||||
error_reason[reason_size - 1] = '\0';
|
||||
return 400; // Bad Request
|
||||
}
|
||||
|
||||
// Get X-Content-Length header
|
||||
const char* length_header = getenv("HTTP_X_CONTENT_LENGTH");
|
||||
if (!length_header) {
|
||||
strncpy(error_reason, XREASON_MISSING_LENGTH, reason_size - 1);
|
||||
error_reason[reason_size - 1] = '\0';
|
||||
return 411; // Length Required
|
||||
}
|
||||
|
||||
// Validate content length
|
||||
long parsed_length;
|
||||
int length_result = validate_content_length(length_header, &parsed_length);
|
||||
if (length_result == 0) {
|
||||
if (parsed_length == 0) {
|
||||
strncpy(error_reason, XREASON_ZERO_LENGTH, reason_size - 1);
|
||||
} else {
|
||||
strncpy(error_reason, XREASON_INVALID_LENGTH, reason_size - 1);
|
||||
}
|
||||
error_reason[reason_size - 1] = '\0';
|
||||
return 400; // Bad Request
|
||||
} else if (length_result == -1) {
|
||||
strncpy(error_reason, XREASON_FILE_TOO_LARGE, reason_size - 1);
|
||||
error_reason[reason_size - 1] = '\0';
|
||||
return 413; // Content Too Large
|
||||
}
|
||||
|
||||
// Get X-Content-Type header (optional)
|
||||
const char* type_header = getenv("HTTP_X_CONTENT_TYPE");
|
||||
|
||||
// Set output values
|
||||
*sha256 = sha256_header;
|
||||
*content_type = type_header ? type_header : "application/octet-stream";
|
||||
*content_length = parsed_length;
|
||||
|
||||
return 200; // Success
|
||||
}
|
||||
|
||||
// Main BUD-06 handler function
|
||||
void handle_head_upload_request(void) {
|
||||
// Log the incoming request
|
||||
log_request("HEAD", "/upload", "pending", 0);
|
||||
|
||||
// Validate upload headers
|
||||
const char* sha256 = NULL;
|
||||
const char* content_type = NULL;
|
||||
long content_length = 0;
|
||||
char error_reason[256];
|
||||
|
||||
int validation_result = validate_upload_headers(&sha256, &content_type,
|
||||
&content_length, error_reason, sizeof(error_reason));
|
||||
|
||||
if (validation_result != 200) {
|
||||
// Header validation failed
|
||||
const char* error_type;
|
||||
switch (validation_result) {
|
||||
case 400: error_type = "invalid_headers"; break;
|
||||
case 411: error_type = "length_required"; break;
|
||||
case 413: error_type = "payload_too_large"; break;
|
||||
default: error_type = "validation_error"; break;
|
||||
}
|
||||
|
||||
send_upload_error_response(validation_result, error_type, error_reason, error_reason);
|
||||
log_request("HEAD", "/upload", "none", validation_result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if blob already exists (duplicate detection)
|
||||
if (check_blob_exists(sha256)) {
|
||||
send_upload_error_response(409, "blob_exists", "Blob with this hash already exists", XREASON_BLOB_EXISTS);
|
||||
log_request("HEAD", "/upload", "none", 409);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for optional authorization
|
||||
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
||||
const char* auth_status = "none";
|
||||
|
||||
if (auth_header) {
|
||||
// Validate authorization if provided
|
||||
nostr_request_t request = {
|
||||
.operation = "upload",
|
||||
.auth_header = auth_header,
|
||||
.event = NULL,
|
||||
.resource_hash = sha256,
|
||||
.mime_type = content_type,
|
||||
.file_size = content_length,
|
||||
.client_ip = getenv("REMOTE_ADDR"),
|
||||
.app_context = NULL
|
||||
};
|
||||
|
||||
nostr_request_result_t result;
|
||||
int auth_result = nostr_validate_request(&request, &result);
|
||||
|
||||
if (auth_result != NOSTR_SUCCESS || !result.valid) {
|
||||
const char* error_type = "authentication_failed";
|
||||
const char* message = "Invalid or expired authentication";
|
||||
const char* details = result.reason[0] ? result.reason : "Authentication validation failed";
|
||||
|
||||
// Provide more specific error messages based on the reason
|
||||
if (strstr(result.reason, "whitelist")) {
|
||||
error_type = "pubkey_not_whitelisted";
|
||||
message = "Public key not authorized";
|
||||
details = result.reason;
|
||||
} else if (strstr(result.reason, "blacklist")) {
|
||||
error_type = "access_denied";
|
||||
message = "Access denied by policy";
|
||||
details = result.reason;
|
||||
} else if (strstr(result.reason, "size")) {
|
||||
error_type = "file_too_large";
|
||||
message = "File size exceeds policy limits";
|
||||
details = result.reason;
|
||||
}
|
||||
|
||||
send_upload_error_response(401, error_type, message, details);
|
||||
log_request("HEAD", "/upload", "auth_failed", 401);
|
||||
return;
|
||||
}
|
||||
auth_status = "authenticated";
|
||||
}
|
||||
|
||||
// All validations passed - return success
|
||||
send_upload_success_response(sha256, content_type, content_length);
|
||||
log_request("HEAD", "/upload", auth_status, 200);
|
||||
}
|
||||
Reference in New Issue
Block a user