/* * BUD-06 Upload Requirements (Pre-flight Validation) * Handles HEAD /upload requests for upload requirement validation */ #include #include #include #include #include "ginxsom.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 for authorization first (before duplicate detection) const char* auth_header = getenv("HTTP_AUTHORIZATION"); const char* auth_status = "none"; if (auth_header) { // NOTE: Authorization validation now handled by centralized validation system in main.c // This handler receives pre-validated requests, so if we reach here with auth_header, // the authentication was already successful auth_status = "authenticated"; } else { // Check if server requires authorization for uploads // If auth is required but not provided, return 401 before checking for duplicates // TODO: This should check server configuration for auth requirements // For now, assume auth is optional unless configured otherwise } // Check if blob already exists (duplicate detection - after auth validation) 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", auth_status, 409); return; } // All validations passed - return success send_upload_success_response(sha256, content_type, content_length); log_request("HEAD", "/upload", auth_status, 200); }