'failure', 'message' => 'Method not allowed']); exit; } $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? ''; $expectedKey = getenv('NABEH_API_KEY') ?: ''; if (empty($apiKey) || $apiKey !== $expectedKey) { http_response_code(401); echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']); exit; } // Rate limiting $rateLimitFile = __DIR__ . '/../logs/nabeh_upload_rate_' . md5($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.lock'; $rateLimitWindow = 60; $rateLimitMax = 10; $nowTime = time(); $attempts = []; if (file_exists($rateLimitFile)) { $attempts = json_decode(file_get_contents($rateLimitFile), true) ?: []; $attempts = array_filter($attempts, fn($t) => $t > ($nowTime - $rateLimitWindow)); } if (count($attempts) >= $rateLimitMax) { http_response_code(429); echo json_encode(['status' => 'failure', 'message' => 'Too many requests. Try again later.']); exit; } $attempts[] = $nowTime; file_put_contents($rateLimitFile, json_encode($attempts), LOCK_EX); const MAX_FILE_MB = 5; const ALLOWED_MIMES = ['image/jpeg', 'image/png', 'image/webp']; const UPLOAD_ROOT = __DIR__ . '/../private_uploads'; const SIGNED_TTL_SEC = 172800; $signSecret = getenv('SECRET_KEY_HMAC') ?: ''; if (empty($signSecret)) { uploadLog('[Nabeh Upload] SECRET_KEY_HMAC not configured', 'ERROR'); http_response_code(500); echo json_encode(['status' => 'failure', 'message' => 'Server configuration error']); exit; } $host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com'; $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; define('PUBLIC_BASE', "$protocol://$host/siro"); if (!is_dir(UPLOAD_ROOT)) { @mkdir(UPLOAD_ROOT, 0700, true); } uploadLog("[Nabeh Upload] Document upload started"); $allowedDocTypes = [ 'id_front', 'id_back', 'driver_license_front', 'driver_license_back', 'car_license_front', 'car_license_back', 'criminal_record', 'profile_picture', ]; $driverId = $_POST['driver_id'] ?? ''; $docType = $_POST['doc_type'] ?? ''; if (empty($driverId) || empty($docType)) { jsonError('driver_id and doc_type are required.'); } $driverIdSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', $driverId); if (!in_array($docType, $allowedDocTypes, true)) { jsonError("Invalid doc_type. Allowed: " . implode(', ', $allowedDocTypes)); } if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { $errCode = $_FILES['file']['error'] ?? 'missing_file'; uploadLog("[Nabeh Upload] File upload error. Code: $errCode", 'ERROR'); jsonError('No file uploaded or upload error.'); } $tmpPath = $_FILES['file']['tmp_name']; $size = filesize($tmpPath); if ($size === false || $size <= 0) { jsonError('Invalid file size.'); } if ($size > MAX_FILE_MB * 1024 * 1024) { jsonError('File too large. Max ' . MAX_FILE_MB . ' MB.'); } $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($tmpPath) ?: 'application/octet-stream'; if (!in_array($mime, ALLOWED_MIMES, true)) { jsonError("Unsupported file type: $mime"); } $extMap = [ 'image/jpeg' => '.jpg', 'image/png' => '.png', 'image/webp' => '.webp', ]; $ext = $extMap[$mime]; $h = hash('sha1', $driverIdSafe); $subdir = substr($h, 0, 2) . '/' . substr($h, 2, 2); $destDir = UPLOAD_ROOT . '/' . $subdir; if (!is_dir($destDir)) { @mkdir($destDir, 0700, true); } $serverName = "{$driverIdSafe}__{$docType}{$ext}"; $destPath = $destDir . '/' . $serverName; $resolvedDest = realpath($destPath) ?: $destPath; $resolvedRoot = realpath(UPLOAD_ROOT) ?: UPLOAD_ROOT; if (is_file($destPath) && str_starts_with($resolvedDest, $resolvedRoot)) { @unlink($destPath); } if (!move_uploaded_file($tmpPath, $destPath)) { jsonError('Failed to save the uploaded file.'); } @chmod($destPath, 0600); $extShort = ltrim($ext, '.'); $expires = time() + SIGNED_TTL_SEC; $message = $driverIdSafe . ':' . $docType . ':' . $extShort . ':' . $expires; $signature = hash_hmac('sha256', $message, $signSecret); $fileUrl = PUBLIC_BASE . '/secure_image.php' . '?driver_id=' . urlencode($driverIdSafe) . '&doc_type=' . urlencode($docType) . '&ext=' . urlencode($extShort) . '&expires=' . $expires . '&signature=' . urlencode($signature); uploadLog("[Nabeh Upload] Document uploaded successfully. Type: $docType, Size: $size"); printSuccess([ 'status' => 'success', 'success_file' => true, 'file_url' => $fileUrl, 'file_name' => $serverName, 'driver_id' => $driverIdSafe, 'doc_type' => $docType, 'mime_type' => $mime, 'size_bytes' => $size, 'expires_at' => date('c', $expires), ]);