233 lines
8.4 KiB
C
233 lines
8.4 KiB
C
/*
|
|
* 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"
|
|
|
|
// 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);
|
|
} |