Add: early quota check

This commit is contained in:
hyzen
2026-02-17 01:03:48 +05:30
parent 4a7705d928
commit 46a9d3061d

View File

@@ -3,7 +3,7 @@
// CONFIGURATION // CONFIGURATION
// ------------------------- // -------------------------
$AUTH_USER = 'user'; $AUTH_USER = 'user';
$AUTH_PASS = 'pass'; $AUTH_PASS = 'pass'; // Change this to a strong password
$UPLOAD_DIR = __DIR__ . '/files/'; $UPLOAD_DIR = __DIR__ . '/files/';
//$ALLOWED_TYPES = ['image/png', 'image/jpeg', 'application/pdf', 'text/plain']; // optional //$ALLOWED_TYPES = ['image/png', 'image/jpeg', 'application/pdf', 'text/plain']; // optional
$ALLOWED_TYPES = []; $ALLOWED_TYPES = [];
@@ -35,17 +35,17 @@ function get_client_ip() {
function check_rate_limit($ip, $file_size) { function check_rate_limit($ip, $file_size) {
global $RATE_LIMIT_DIR, $RATE_LIMIT, $RATE_LIMIT_PERIOD; global $RATE_LIMIT_DIR, $RATE_LIMIT, $RATE_LIMIT_PERIOD;
if (!is_dir($RATE_LIMIT_DIR)) { if (!is_dir($RATE_LIMIT_DIR)) {
mkdir($RATE_LIMIT_DIR, 0700, true); mkdir($RATE_LIMIT_DIR, 0700, true);
} }
$safe_ip = preg_replace('/[^a-zA-Z0-9\-_\.]/', '_', $ip); $safe_ip = preg_replace('/[^a-zA-Z0-9\-_\.]/', '_', $ip);
$rate_file = $RATE_LIMIT_DIR . $safe_ip . '.json'; $rate_file = $RATE_LIMIT_DIR . $safe_ip . '.json';
$current_time = time(); $current_time = time();
$usage_data = []; $usage_data = [];
if (file_exists($rate_file)) { if (file_exists($rate_file)) {
$content = file_get_contents($rate_file); $content = file_get_contents($rate_file);
$usage_data = json_decode($content, true); $usage_data = json_decode($content, true);
@@ -53,48 +53,48 @@ function check_rate_limit($ip, $file_size) {
$usage_data = []; $usage_data = [];
} }
} }
$usage_data = array_filter($usage_data, function($entry) use ($current_time, $RATE_LIMIT_PERIOD) { $usage_data = array_filter($usage_data, function($entry) use ($current_time, $RATE_LIMIT_PERIOD) {
return ($current_time - $entry['timestamp']) < $RATE_LIMIT_PERIOD; return ($current_time - $entry['timestamp']) < $RATE_LIMIT_PERIOD;
}); });
$total_usage = array_sum(array_column($usage_data, 'size')); $total_usage = array_sum(array_column($usage_data, 'size'));
if ($total_usage + $file_size > $RATE_LIMIT) { if ($total_usage + $file_size > $RATE_LIMIT) {
return false; return false;
} }
$usage_data[] = [ $usage_data[] = [
'timestamp' => $current_time, 'timestamp' => $current_time,
'size' => $file_size, 'size' => $file_size
]; ];
file_put_contents($rate_file, json_encode($usage_data, JSON_PRETTY_PRINT)); file_put_contents($rate_file, json_encode($usage_data, JSON_PRETTY_PRINT));
return true; return true;
} }
function get_remaining_quota($ip) { function get_remaining_quota($ip) {
global $RATE_LIMIT_DIR, $RATE_LIMIT, $RATE_LIMIT_PERIOD; global $RATE_LIMIT_DIR, $RATE_LIMIT, $RATE_LIMIT_PERIOD;
$safe_ip = preg_replace('/[^a-zA-Z0-9\-_\.]/', '_', $ip); $safe_ip = preg_replace('/[^a-zA-Z0-9\-_\.]/', '_', $ip);
$rate_file = $RATE_LIMIT_DIR . $safe_ip . '.json'; $rate_file = $RATE_LIMIT_DIR . $safe_ip . '.json';
if (!file_exists($rate_file)) { if (!file_exists($rate_file)) {
return $RATE_LIMIT; return $RATE_LIMIT;
} }
$content = file_get_contents($rate_file); $content = file_get_contents($rate_file);
$usage_data = json_decode($content, true); $usage_data = json_decode($content, true);
if (!is_array($usage_data)) { if (!is_array($usage_data)) {
return $RATE_LIMIT; return $RATE_LIMIT;
} }
$current_time = time(); $current_time = time();
$usage_data = array_filter($usage_data, function($entry) use ($current_time, $RATE_LIMIT_PERIOD) { $usage_data = array_filter($usage_data, function($entry) use ($current_time, $RATE_LIMIT_PERIOD) {
return ($current_time - $entry['timestamp']) < $RATE_LIMIT_PERIOD; return ($current_time - $entry['timestamp']) < $RATE_LIMIT_PERIOD;
}); });
$total_usage = array_sum(array_column($usage_data, 'size')); $total_usage = array_sum(array_column($usage_data, 'size'));
return max(0, $RATE_LIMIT - $total_usage); return max(0, $RATE_LIMIT - $total_usage);
} }
@@ -131,8 +131,8 @@ if ($request_method === 'GET' && isset($_GET['quota'])) {
// ------------------------- // -------------------------
// FILE DOWNLOAD (GET/HEAD) // FILE DOWNLOAD (GET/HEAD)
// ------------------------- // -------------------------
else if ($request_method === 'GET' || $request_method === 'HEAD') { if ($request_method === 'GET' || $request_method === 'HEAD') {
$upload_file_name = substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME']) + 1); $upload_file_name = substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME'])+1);
$sanitized_name = basename($upload_file_name); $sanitized_name = basename($upload_file_name);
$store_file_name = $UPLOAD_DIR . $sanitized_name; $store_file_name = $UPLOAD_DIR . $sanitized_name;
@@ -142,13 +142,13 @@ else if ($request_method === 'GET' || $request_method === 'HEAD') {
$mime_type = 'application/octet-stream'; $mime_type = 'application/octet-stream';
header('Content-Disposition: attachment'); header('Content-Disposition: attachment');
} }
header('Content-Type: ' . $mime_type); header('Content-Type: ' . $mime_type);
header('Content-Length: ' . filesize($store_file_name)); header('Content-Length: ' . filesize($store_file_name));
header("Content-Security-Policy: default-src 'none'"); header("Content-Security-Policy: default-src 'none'");
header("X-Content-Security-Policy: default-src 'none'"); header("X-Content-Security-Policy: default-src 'none'");
header("X-WebKit-CSP: default-src 'none'"); header("X-WebKit-CSP: default-src 'none'");
if ($request_method !== 'HEAD') { if ($request_method !== 'HEAD') {
readfile($store_file_name); readfile($store_file_name);
} }
@@ -161,16 +161,16 @@ else if ($request_method === 'GET' || $request_method === 'HEAD') {
// ------------------------- // -------------------------
// OPTIONS (CORS preflight) // OPTIONS (CORS preflight)
// ------------------------- // -------------------------
else if ($request_method === 'OPTIONS') { if ($request_method === 'OPTIONS') {
exit; exit;
} }
// ------------------------- // -------------------------
// BASIC HTTP AUTH (for POST uploads/deletes) // BASIC HTTP AUTH (for POST uploads/deletes)
// ------------------------- // -------------------------
else if ($request_method === 'POST') { if ($request_method === 'POST') {
if (!isset($_SERVER['PHP_AUTH_USER']) || if (!isset($_SERVER['PHP_AUTH_USER']) ||
$_SERVER['PHP_AUTH_USER'] !== $AUTH_USER || $_SERVER['PHP_AUTH_USER'] !== $AUTH_USER ||
$_SERVER['PHP_AUTH_PW'] !== $AUTH_PASS) { $_SERVER['PHP_AUTH_PW'] !== $AUTH_PASS) {
header('WWW-Authenticate: Basic realm="Uploader"'); header('WWW-Authenticate: Basic realm="Uploader"');
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
@@ -178,16 +178,31 @@ else if ($request_method === 'POST') {
exit; exit;
} }
// -------------------------
// EARLY QUOTA CHECK (using Content-Length, before file is buffered)
// This rejects oversized uploads immediately for both web and CLI uploads.
// -------------------------
$content_length = isset($_SERVER['CONTENT_LENGTH']) ? (int)$_SERVER['CONTENT_LENGTH'] : 0;
if ($content_length > 0) {
$client_ip = get_client_ip();
$remaining = get_remaining_quota($client_ip);
if ($content_length > $remaining) {
http_response_code(429);
echo 'Rate limit exceeded. You have ' . round($remaining / 1024 / 1024, 2) . ' MB remaining in your 24-hour quota.';
exit;
}
}
// ------------------------- // -------------------------
// FILE DELETE HANDLING // FILE DELETE HANDLING
// ------------------------- // -------------------------
if (isset($_POST['delete'])) { if (isset($_POST['delete'])) {
$fileToDelete = basename($_POST['delete']); $fileToDelete = basename($_POST['delete']); // sanitize filename
$target = $UPLOAD_DIR . $fileToDelete; $target = $UPLOAD_DIR . $fileToDelete;
if (file_exists($target)) { if (file_exists($target)) {
if (unlink($target)) { if (unlink($target)) {
@unlink($target . '-type'); @unlink($target . '-type'); // Also delete type file if exists
echo 'Deleted successfully'; echo 'Deleted successfully';
exit; exit;
} else { } else {
@@ -223,7 +238,9 @@ else if ($request_method === 'POST') {
} }
// Rate limiting check // Rate limiting check
// This records the usage after the early check already passed.
$client_ip = get_client_ip(); $client_ip = get_client_ip();
if (!check_rate_limit($client_ip, $file['size'])) { if (!check_rate_limit($client_ip, $file['size'])) {
$remaining = get_remaining_quota($client_ip); $remaining = get_remaining_quota($client_ip);
http_response_code(429); http_response_code(429);
@@ -231,7 +248,7 @@ else if ($request_method === 'POST') {
exit; exit;
} }
// Check if upload directory exists // Check if upload directory exists (normal files)
if (!is_dir($UPLOAD_DIR)) { if (!is_dir($UPLOAD_DIR)) {
http_response_code(500); http_response_code(500);
echo 'Upload directory not available.'; echo 'Upload directory not available.';
@@ -251,11 +268,12 @@ else if ($request_method === 'POST') {
} }
$originalName = basename($file['name']); $originalName = basename($file['name']);
// Sanitize original filename (remove extension first)
$ext = pathinfo($originalName, PATHINFO_EXTENSION); $ext = pathinfo($originalName, PATHINFO_EXTENSION);
$nameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME); $nameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME);
$nameWithoutExt = preg_replace('/[^A-Za-z0-9_\-]/', '_', $nameWithoutExt); $nameWithoutExt = preg_replace('/[^A-Za-z0-9_\-]/', '_', $nameWithoutExt);
// Generate unique filename // Generate random string and append
do { do {
$random = random_string(); $random = random_string();
$filename = $nameWithoutExt . '_' . $random . ($ext ? '.' . $ext : ''); $filename = $nameWithoutExt . '_' . $random . ($ext ? '.' . $ext : '');
@@ -269,7 +287,7 @@ else if ($request_method === 'POST') {
exit; exit;
} }
// Store MIME type // Store MIME type for consistency
file_put_contents($target . '-type', $file['type']); file_put_contents($target . '-type', $file['type']);
// Return public URL // Return public URL
@@ -288,7 +306,5 @@ else if ($request_method === 'POST') {
// ------------------------- // -------------------------
// INVALID REQUEST METHOD // INVALID REQUEST METHOD
// ------------------------- // -------------------------
else { header('HTTP/1.0 400 Bad Request');
header('HTTP/1.0 400 Bad Request');
}
?> ?>