Попробуйте наше меню - бесплатно
Идеальные события начинаются с идеального вкуса
Telegram
Max
0, 'path' => '/', 'secure' => $httpsOn, // cookie только по HTTPS 'httponly' => true, // недоступна из JS 'samesite' => 'Strict', // защита от CSRF ]); session_start(); // ==== Транслитерация кириллицы -> латиница ==== function transliterate(string $text): string { $map = [ 'а'=>'a','б'=>'b','в'=>'v','г'=>'g','д'=>'d','е'=>'e','ё'=>'e','ж'=>'zh', 'з'=>'z','и'=>'i','й'=>'y','к'=>'k','л'=>'l','м'=>'m','н'=>'n','о'=>'o', 'п'=>'p','р'=>'r','с'=>'s','т'=>'t','у'=>'u','ф'=>'f','х'=>'kh','ц'=>'ts', 'ч'=>'ch','ш'=>'sh','щ'=>'shch','ъ'=>'','ы'=>'y','ь'=>'','э'=>'e','ю'=>'yu','я'=>'ya', 'А'=>'A','Б'=>'B','В'=>'V','Г'=>'G','Д'=>'D','Е'=>'E','Ё'=>'E','Ж'=>'Zh', 'З'=>'Z','И'=>'I','Й'=>'Y','К'=>'K','Л'=>'L','М'=>'M','Н'=>'N','О'=>'O', 'П'=>'P','Р'=>'R','С'=>'S','Т'=>'T','У'=>'U','Ф'=>'F','Х'=>'Kh','Ц'=>'Ts', 'Ч'=>'Ch','Ш'=>'Sh','Щ'=>'Shch','Ъ'=>'','Ы'=>'Y','Ь'=>'','Э'=>'E','Ю'=>'Yu','Я'=>'Ya', // украинские/белорусские на всякий случай 'і'=>'i','І'=>'I','ї'=>'yi','Ї'=>'Yi','є'=>'ye','Є'=>'Ye','ґ'=>'g','Ґ'=>'G','ў'=>'u','Ў'=>'U', ]; return strtr($text, $map); } // ==== Очистка имени файла ==== function sanitizeFilename(string $name): string { $name = transliterate($name); $ext = pathinfo($name, PATHINFO_EXTENSION); $base = pathinfo($name, PATHINFO_FILENAME); // пробелы -> подчёркивания, удаляем всё кроме безопасных символов $base = preg_replace('/\s+/u', '_', $base); $base = preg_replace('/[^A-Za-z0-9._-]/', '', $base); $base = trim($base, '.-_'); if ($base === '') { $base = 'file_' . date('Ymd_His'); } $ext = preg_replace('/[^A-Za-z0-9]/', '', $ext); return $ext !== '' ? $base . '.' . strtolower($ext) : $base; } // ==== Уникальное имя, чтобы не перезаписать существующее ==== function uniqueFilename(string $dir, string $filename): string { $ext = pathinfo($filename, PATHINFO_EXTENSION); $base = pathinfo($filename, PATHINFO_FILENAME); $candidate = $filename; $i = 1; while (file_exists($dir . DIRECTORY_SEPARATOR . $candidate)) { $candidate = $ext !== '' ? "{$base}-{$i}.{$ext}" : "{$base}-{$i}"; $i++; } return $candidate; } // ==== Работа с папками ==== // Очистка одного сегмента пути (имени папки) function sanitizeSegment(string $seg): string { $seg = transliterate($seg); $seg = preg_replace('/\s+/u', '_', $seg); $seg = preg_replace('/[^A-Za-z0-9._-]/', '', $seg); return trim($seg, '.-_'); } // Приводит относительный путь папки к безопасному виду (без выхода за пределы корня) function normalizeFolder(string $rel): string { $rel = str_replace('\\', '/', $rel); $parts = []; foreach (explode('/', $rel) as $seg) { if ($seg === '' || $seg === '.' || $seg === '..') { continue; } $clean = sanitizeSegment($seg); if ($clean !== '') { $parts[] = $clean; } } return implode('/', $parts); } // Список всех подпапок относительно корня (с ограничением глубины) function listFolders(string $base, string $prefix = '', int $depth = 0): array { if ($depth > 6 || !is_dir($base)) { return []; } $result = []; $entries = @scandir($base) ?: []; foreach ($entries as $entry) { if ($entry === '.' || $entry === '..' || $entry[0] === '.') { continue; } $full = $base . DIRECTORY_SEPARATOR . $entry; if (is_dir($full)) { $rel = $prefix === '' ? $entry : $prefix . '/' . $entry; $result[] = $rel; $result = array_merge($result, listFolders($full, $rel, $depth + 1)); } } return $result; } // Собирает публичный URL с учётом относительного пути папки function buildPublicUrl(string $folderRel, string $filename): string { $url = rtrim(PUBLIC_BASE_URL, '/') . '/'; if ($folderRel !== '') { $url .= implode('/', array_map('rawurlencode', explode('/', $folderRel))) . '/'; } return $url . rawurlencode($filename); } function isAuthenticated(): bool { return !empty($_SESSION['authenticated']); } // ==== Защита от перебора пароля (по IP, хранится во временной папке вне веб-корня) ==== function throttlePath(): string { $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'uploader_login_' . md5($ip) . '.json'; } function throttleState(): array { $f = throttlePath(); if (is_file($f)) { $d = json_decode((string)@file_get_contents($f), true); if (is_array($d)) { return ['attempts' => (int)($d['attempts'] ?? 0), 'lock_until' => (int)($d['lock_until'] ?? 0)]; } } return ['attempts' => 0, 'lock_until' => 0]; } function throttleSave(int $attempts, int $lockUntil): void { @file_put_contents(throttlePath(), json_encode(['attempts' => $attempts, 'lock_until' => $lockUntil]), LOCK_EX); } function jsonResponse(array $data, int $code = 200): void { http_response_code($code); header('Content-Type: application/json; charset=utf-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } // ==== Маршрутизация ==== $action = $_GET['action'] ?? ($_POST['action'] ?? ''); // Выход if ($action === 'logout') { $_SESSION = []; session_destroy(); header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?')); exit; } // Логин if ($action === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') { $now = time(); $state = throttleState(); // блокировка активна — сразу отказ if ($state['lock_until'] > $now) { $wait = $state['lock_until'] - $now; jsonResponse(['ok' => false, 'error' => "Слишком много попыток. Повторите через {$wait} сек."], 429); } $pass = $_POST['password'] ?? ''; if (password_verify((string)$pass, PASSWORD_HASH)) { throttleSave(0, 0); // сброс счётчика session_regenerate_id(true); // защита от фиксации сессии $_SESSION['authenticated'] = true; jsonResponse(['ok' => true]); } // неудача — наращиваем счётчик и при превышении включаем блокировку $attempts = $state['attempts'] + 1; $lockUntil = 0; if ($attempts >= LOGIN_MAX_ATTEMPTS) { $lockUntil = $now + min(LOGIN_LOCK_MAX, 30 * (2 ** ($attempts - LOGIN_MAX_ATTEMPTS))); } throttleSave($attempts, $lockUntil); usleep(400000); // небольшая задержка против быстрого перебора if ($lockUntil > $now) { $wait = $lockUntil - $now; jsonResponse(['ok' => false, 'error' => "Слишком много попыток. Повторите через {$wait} сек."], 429); } jsonResponse(['ok' => false, 'error' => 'Неверный пароль'], 401); } // Список папок if ($action === 'folders') { if (!isAuthenticated()) { jsonResponse(['ok' => false, 'error' => 'Не авторизован'], 401); } $folders = listFolders(UPLOAD_DIR); sort($folders, SORT_NATURAL | SORT_FLAG_CASE); jsonResponse(['ok' => true, 'folders' => $folders]); } // Создание папки if ($action === 'mkdir' && $_SERVER['REQUEST_METHOD'] === 'POST') { if (!isAuthenticated()) { jsonResponse(['ok' => false, 'error' => 'Не авторизован'], 401); } $parent = normalizeFolder($_POST['parent'] ?? ''); $name = sanitizeSegment($_POST['name'] ?? ''); if ($name === '') { jsonResponse(['ok' => false, 'error' => 'Некорректное имя папки'], 400); } $rel = $parent === '' ? $name : $parent . '/' . $name; $abs = UPLOAD_DIR . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel); if (is_dir($abs)) { jsonResponse(['ok' => true, 'folder' => $rel, 'existed' => true]); } if (!mkdir($abs, 0755, true) && !is_dir($abs)) { jsonResponse(['ok' => false, 'error' => 'Не удалось создать папку'], 500); } jsonResponse(['ok' => true, 'folder' => $rel]); } // Загрузка файла if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') { if (!isAuthenticated()) { jsonResponse(['ok' => false, 'error' => 'Не авторизован'], 401); } if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { $errCode = $_FILES['file']['error'] ?? -1; jsonResponse(['ok' => false, 'error' => 'Ошибка загрузки (код ' . $errCode . ')'], 400); } $file = $_FILES['file']; if (MAX_FILE_SIZE > 0 && $file['size'] > MAX_FILE_SIZE) { jsonResponse(['ok' => false, 'error' => 'Файл слишком большой'], 400); } // целевая папка (по умолчанию — корень) $folderRel = normalizeFolder($_POST['folder'] ?? ''); $destDir = UPLOAD_DIR . ($folderRel !== '' ? DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $folderRel) : ''); if ($folderRel !== '' && !is_dir($destDir)) { if (!mkdir($destDir, 0755, true) && !is_dir($destDir)) { jsonResponse(['ok' => false, 'error' => 'Не удалось создать папку'], 500); } } $clean = sanitizeFilename($file['name']); $replace = !empty($_POST['replace']) && $_POST['replace'] !== '0' && $_POST['replace'] !== 'false'; // при включённой замене пишем поверх существующего, иначе подбираем уникальное имя $finalName = $replace ? $clean : uniqueFilename($destDir, $clean); $target = $destDir . DIRECTORY_SEPARATOR . $finalName; if (!move_uploaded_file($file['tmp_name'], $target)) { jsonResponse(['ok' => false, 'error' => 'Не удалось сохранить файл'], 500); } jsonResponse([ 'ok' => true, 'name' => $finalName, 'original' => $file['name'], 'folder' => $folderRel, 'url' => buildPublicUrl($folderRel, $finalName), 'size' => $file['size'], ]); } ?> Загрузка файлов

Вход

Введите пароль для доступа к загрузчику

Загрузка файлов

Перетащите файлы — ссылки появятся ниже
⬆️
Перетащите файлы сюда
или нажмите, чтобы выбрать