update ui
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
upload/
|
||||||
|
upload_chunks/
|
||||||
70
config.php
70
config.php
@@ -1,4 +1,72 @@
|
|||||||
<?php
|
<?php
|
||||||
define('PASSWORD', 'your_password_here');
|
// Хешированный пароль (используйте password_hash() для генерации)
|
||||||
|
// Пример для пароля "mypassword123": password_hash('mypassword123', PASSWORD_DEFAULT)
|
||||||
|
define('PASSWORD_HASH', '$2y$12$54SMO7iJ/W2d3LKFGviGEuzD67qlsLpfGxYAh7rS5TEcG5hVO3bP.'); // замените на свой хеш
|
||||||
|
|
||||||
define('UPLOAD_DIR', __DIR__ . '/upload/');
|
define('UPLOAD_DIR', __DIR__ . '/upload/');
|
||||||
define('CHUNK_DIR', __DIR__ . '/upload_chunks/');
|
define('CHUNK_DIR', __DIR__ . '/upload_chunks/');
|
||||||
|
|
||||||
|
// Максимальный размер файла (можно указать как '10GB', '500MB', '50KB')
|
||||||
|
define('MAX_FILE_SIZE', '10GB');
|
||||||
|
|
||||||
|
// Размер чанка для загрузки (можно указать как '10MB', '1MB', '512KB')
|
||||||
|
define('CHUNK_SIZE', '2MB');
|
||||||
|
|
||||||
|
// Разрешенные расширения файлов (whitelist)
|
||||||
|
define('ALLOWED_EXTENSIONS', [
|
||||||
|
// Изображения
|
||||||
|
'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff', 'tif', 'ico', 'heic', 'heif', 'avif',
|
||||||
|
|
||||||
|
// Документы
|
||||||
|
'pdf', 'doc', 'docx', 'txt', 'rtf', 'odt', 'ods', 'odp', 'pages', 'key', 'numbers',
|
||||||
|
'ppt', 'pptx', 'xps', 'epub', 'mobi', 'fb2',
|
||||||
|
|
||||||
|
// Видео
|
||||||
|
'mp4', 'avi', 'mkv', 'mov', 'webm', 'flv', 'wmv', 'mpg', 'mpeg', 'm4v', '3gp', 'ogv', 'ts', 'mts',
|
||||||
|
|
||||||
|
// Аудио
|
||||||
|
'mp3', 'wav', 'flac', 'ogg', 'aac', 'm4a', 'wma', 'opus', 'oga', 'aiff', 'au', 'ra',
|
||||||
|
|
||||||
|
// Архивы
|
||||||
|
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'lzma', 'cab', 'iso', 'dmg', 'deb', 'rpm',
|
||||||
|
|
||||||
|
// Данные и таблицы
|
||||||
|
'json', 'xml', 'csv', 'xls', 'xlsx', 'ods', 'tsv', 'yaml', 'yml', 'toml', 'ini', 'conf', 'cfg',
|
||||||
|
|
||||||
|
// Код и разработка
|
||||||
|
'html', 'htm', 'css', 'js', 'php', 'py', 'java', 'cpp', 'c', 'h', 'cs', 'rb', 'go', 'rs', 'swift',
|
||||||
|
'kt', 'scala', 'pl', 'sh', 'bat', 'cmd', 'ps1', 'vbs', 'r', 'sql', 'md', 'tex', 'log',
|
||||||
|
|
||||||
|
// Веб и CMS
|
||||||
|
'wpress', 'sql', 'backup', 'bak', 'db', 'sqlite', 'sqlite3',
|
||||||
|
|
||||||
|
// Шрифты
|
||||||
|
'ttf', 'otf', 'woff', 'woff2', 'eot',
|
||||||
|
|
||||||
|
// CAD и дизайн
|
||||||
|
'dwg', 'dxf', 'skp', 'blend', 'obj', 'fbx', '3ds', 'dae', 'stl', 'ply',
|
||||||
|
'psd', 'ai', 'eps', 'indd', 'sketch', 'fig', 'xd',
|
||||||
|
|
||||||
|
// Виртуализация и образы
|
||||||
|
'vmdk', 'vdi', 'vhd', 'vhdx', 'qcow2', 'img', 'bin', 'cue',
|
||||||
|
|
||||||
|
// Мобильные приложения и исполняемые файлы
|
||||||
|
'apk', 'ipa', 'appx', 'msix', 'exe', 'msi', 'deb', 'rpm', 'dmg', 'pkg',
|
||||||
|
|
||||||
|
// Научные данные
|
||||||
|
'mat', 'hdf5', 'nc', 'fits', 'sdf', 'mol', 'pdb',
|
||||||
|
|
||||||
|
// Игры и мультимедиа
|
||||||
|
'unity', 'unitypackage', 'pak', 'wad', 'pk3', 'vpk',
|
||||||
|
|
||||||
|
// Электронные книги и документы
|
||||||
|
'djvu', 'chm', 'hlp',
|
||||||
|
|
||||||
|
// Прочие полезные форматы
|
||||||
|
'torrent', 'magnet', 'desktop', 'lnk', 'url',
|
||||||
|
'ics', 'vcf', 'vcard', 'gpx', 'kml', 'kmz'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// В конце файла config.php добавьте:
|
||||||
|
error_log("Config loaded. MAX_FILE_SIZE: " . MAX_FILE_SIZE);
|
||||||
|
error_log("Config loaded. CHUNK_SIZE: " . CHUNK_SIZE);
|
||||||
|
|||||||
146
functions.php
Normal file
146
functions.php
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Функция для преобразования размера из строки в байты
|
||||||
|
function parseFileSize($size)
|
||||||
|
{
|
||||||
|
if (is_numeric($size)) {
|
||||||
|
return intval($size);
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = strtoupper(trim($size));
|
||||||
|
|
||||||
|
// Проверяем, что строка не пустая
|
||||||
|
if (empty($size)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем отладочную информацию
|
||||||
|
error_log("parseFileSize input: " . $size);
|
||||||
|
|
||||||
|
$units = [
|
||||||
|
'TB' => 1024 ** 4,
|
||||||
|
'GB' => 1024 ** 3,
|
||||||
|
'MB' => 1024 ** 2,
|
||||||
|
'KB' => 1024,
|
||||||
|
'B' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
// Проверяем каждую единицу измерения (начиная с самых больших)
|
||||||
|
foreach ($units as $unit => $multiplier) {
|
||||||
|
if (substr($size, -strlen($unit)) === $unit) {
|
||||||
|
$numberPart = trim(substr($size, 0, -strlen($unit)));
|
||||||
|
error_log("Found unit: $unit, number part: '$numberPart'");
|
||||||
|
|
||||||
|
if (is_numeric($numberPart)) {
|
||||||
|
$result = intval(floatval($numberPart) * $multiplier);
|
||||||
|
error_log("Calculated result: $result bytes");
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если не найдено совпадений с единицами измерения, пробуем преобразовать как число
|
||||||
|
if (is_numeric($size)) {
|
||||||
|
$result = intval($size);
|
||||||
|
error_log("Fallback to numeric: $result");
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Could not parse size: $size, returning 0");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить размер файла в байтах
|
||||||
|
function getMaxFileSize()
|
||||||
|
{
|
||||||
|
$size = parseFileSize(MAX_FILE_SIZE);
|
||||||
|
|
||||||
|
// Отладочная информация
|
||||||
|
error_log("MAX_FILE_SIZE constant: " . MAX_FILE_SIZE);
|
||||||
|
error_log("Parsed max file size: " . $size);
|
||||||
|
|
||||||
|
return $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить размер чанка в байтах
|
||||||
|
function getChunkSize()
|
||||||
|
{
|
||||||
|
$size = parseFileSize(CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Отладочная информация
|
||||||
|
error_log("CHUNK_SIZE constant: " . CHUNK_SIZE);
|
||||||
|
error_log("Parsed chunk size: " . $size);
|
||||||
|
|
||||||
|
return $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для генерации CSRF токена
|
||||||
|
function generateCSRFToken()
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
return $_SESSION['csrf_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для проверки CSRF токена
|
||||||
|
function verifyCSRFToken($token)
|
||||||
|
{
|
||||||
|
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для валидации имени файла
|
||||||
|
function validateFileName($filename)
|
||||||
|
{
|
||||||
|
// Удаляем опасные символы и пути
|
||||||
|
$filename = basename($filename);
|
||||||
|
|
||||||
|
// Проверяем на недопустимые символы
|
||||||
|
if (preg_match('/[<>:"|?*\x00-\x1f]/', $filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем длину
|
||||||
|
if (strlen($filename) > 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем расширение
|
||||||
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($extension, ALLOWED_EXTENSIONS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для получения безопасного имени файла
|
||||||
|
function getSafeFileName($filename)
|
||||||
|
{
|
||||||
|
$filename = basename($filename);
|
||||||
|
$filename = preg_replace('/[<>:"|?*\x00-\x1f]/', '_', $filename);
|
||||||
|
return substr($filename, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для форматирования размера файла
|
||||||
|
function formatFileSize($size)
|
||||||
|
{
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
$unitIndex = 0;
|
||||||
|
while ($size >= 1024 && $unitIndex < count($units) - 1) {
|
||||||
|
$size /= 1024;
|
||||||
|
$unitIndex++;
|
||||||
|
}
|
||||||
|
return round($size, 2) . ' ' . $units[$unitIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для отладки - показать все константы
|
||||||
|
function debugConstants()
|
||||||
|
{
|
||||||
|
error_log("=== DEBUG CONSTANTS ===");
|
||||||
|
error_log("MAX_FILE_SIZE: " . (defined('MAX_FILE_SIZE') ? MAX_FILE_SIZE : 'NOT DEFINED'));
|
||||||
|
error_log("CHUNK_SIZE: " . (defined('CHUNK_SIZE') ? CHUNK_SIZE : 'NOT DEFINED'));
|
||||||
|
error_log("UPLOAD_DIR: " . (defined('UPLOAD_DIR') ? UPLOAD_DIR : 'NOT DEFINED'));
|
||||||
|
error_log("CHUNK_DIR: " . (defined('CHUNK_DIR') ? CHUNK_DIR : 'NOT DEFINED'));
|
||||||
|
error_log("======================");
|
||||||
|
}
|
||||||
579
index.php
579
index.php
@@ -2,86 +2,571 @@
|
|||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
require_once __DIR__ . '/config.php';
|
require_once __DIR__ . '/config.php';
|
||||||
|
require_once __DIR__ . '/functions.php';
|
||||||
|
|
||||||
if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR);
|
if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true);
|
||||||
if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR);
|
if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR, 0755, true);
|
||||||
|
|
||||||
|
// Обработка запросов файлов напрямую из корня
|
||||||
|
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
$requestedFile = ltrim($requestUri, '/');
|
||||||
|
|
||||||
|
// Если запрашивается файл (не административный интерфейс)
|
||||||
|
if (
|
||||||
|
!empty($requestedFile) &&
|
||||||
|
$requestedFile !== 'index.php' &&
|
||||||
|
$requestedFile !== 'upload_chunk.php' &&
|
||||||
|
$requestedFile !== 'merge_chunks.php' &&
|
||||||
|
!isset($_GET['action']) &&
|
||||||
|
!isset($_GET['delete']) &&
|
||||||
|
!isset($_POST['password'])
|
||||||
|
) {
|
||||||
|
|
||||||
|
$filename = basename($requestedFile);
|
||||||
|
$filepath = UPLOAD_DIR . $filename;
|
||||||
|
|
||||||
|
if (file_exists($filepath)) {
|
||||||
|
// Определение MIME типа
|
||||||
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
$mimeTypes = [
|
||||||
|
// Изображения
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
'bmp' => 'image/bmp',
|
||||||
|
'tiff' => 'image/tiff',
|
||||||
|
'tif' => 'image/tiff',
|
||||||
|
'ico' => 'image/x-icon',
|
||||||
|
'heic' => 'image/heic',
|
||||||
|
'heif' => 'image/heif',
|
||||||
|
'avif' => 'image/avif',
|
||||||
|
|
||||||
|
// Документы
|
||||||
|
'pdf' => 'application/pdf',
|
||||||
|
'txt' => 'text/plain',
|
||||||
|
'rtf' => 'application/rtf',
|
||||||
|
'doc' => 'application/msword',
|
||||||
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||||
|
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
'odp' => 'application/vnd.oasis.opendocument.presentation',
|
||||||
|
'ppt' => 'application/vnd.ms-powerpoint',
|
||||||
|
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'epub' => 'application/epub+zip',
|
||||||
|
'mobi' => 'application/x-mobipocket-ebook',
|
||||||
|
|
||||||
|
// Архивы
|
||||||
|
'zip' => 'application/zip',
|
||||||
|
'rar' => 'application/vnd.rar',
|
||||||
|
'7z' => 'application/x-7z-compressed',
|
||||||
|
'tar' => 'application/x-tar',
|
||||||
|
'gz' => 'application/gzip',
|
||||||
|
'bz2' => 'application/x-bzip2',
|
||||||
|
'xz' => 'application/x-xz',
|
||||||
|
'iso' => 'application/x-iso9660-image',
|
||||||
|
|
||||||
|
// Видео
|
||||||
|
'mp4' => 'video/mp4',
|
||||||
|
'avi' => 'video/x-msvideo',
|
||||||
|
'mkv' => 'video/x-matroska',
|
||||||
|
'mov' => 'video/quicktime',
|
||||||
|
'webm' => 'video/webm',
|
||||||
|
'flv' => 'video/x-flv',
|
||||||
|
'wmv' => 'video/x-ms-wmv',
|
||||||
|
'mpg' => 'video/mpeg',
|
||||||
|
'mpeg' => 'video/mpeg',
|
||||||
|
'm4v' => 'video/x-m4v',
|
||||||
|
'3gp' => 'video/3gpp',
|
||||||
|
'ogv' => 'video/ogg',
|
||||||
|
|
||||||
|
// Аудио
|
||||||
|
'mp3' => 'audio/mpeg',
|
||||||
|
'wav' => 'audio/wav',
|
||||||
|
'flac' => 'audio/flac',
|
||||||
|
'ogg' => 'audio/ogg',
|
||||||
|
'aac' => 'audio/aac',
|
||||||
|
'm4a' => 'audio/mp4',
|
||||||
|
'wma' => 'audio/x-ms-wma',
|
||||||
|
'opus' => 'audio/opus',
|
||||||
|
'aiff' => 'audio/aiff',
|
||||||
|
|
||||||
|
// Данные
|
||||||
|
'json' => 'application/json',
|
||||||
|
'xml' => 'application/xml',
|
||||||
|
'csv' => 'text/csv',
|
||||||
|
'xls' => 'application/vnd.ms-excel',
|
||||||
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'yaml' => 'text/yaml',
|
||||||
|
'yml' => 'text/yaml',
|
||||||
|
'toml' => 'text/toml',
|
||||||
|
|
||||||
|
// Код
|
||||||
|
'html' => 'text/html',
|
||||||
|
'htm' => 'text/html',
|
||||||
|
'css' => 'text/css',
|
||||||
|
'js' => 'text/javascript',
|
||||||
|
'php' => 'text/x-php',
|
||||||
|
'py' => 'text/x-python',
|
||||||
|
'java' => 'text/x-java-source',
|
||||||
|
'cpp' => 'text/x-c++src',
|
||||||
|
'c' => 'text/x-csrc',
|
||||||
|
'h' => 'text/x-chdr',
|
||||||
|
'cs' => 'text/x-csharp',
|
||||||
|
'rb' => 'text/x-ruby',
|
||||||
|
'go' => 'text/x-go',
|
||||||
|
'rs' => 'text/x-rust',
|
||||||
|
'sql' => 'text/x-sql',
|
||||||
|
'md' => 'text/markdown',
|
||||||
|
'log' => 'text/plain',
|
||||||
|
'sh' => 'text/x-shellscript',
|
||||||
|
'bat' => 'text/x-msdos-batch',
|
||||||
|
|
||||||
|
// Веб и CMS
|
||||||
|
'wpress' => 'application/octet-stream',
|
||||||
|
'backup' => 'application/octet-stream',
|
||||||
|
'bak' => 'application/octet-stream',
|
||||||
|
'db' => 'application/x-sqlite3',
|
||||||
|
'sqlite' => 'application/x-sqlite3',
|
||||||
|
'sqlite3' => 'application/x-sqlite3',
|
||||||
|
|
||||||
|
// Шрифты
|
||||||
|
'ttf' => 'font/ttf',
|
||||||
|
'otf' => 'font/otf',
|
||||||
|
'woff' => 'font/woff',
|
||||||
|
'woff2' => 'font/woff2',
|
||||||
|
'eot' => 'application/vnd.ms-fontobject',
|
||||||
|
|
||||||
|
// Дизайн
|
||||||
|
'psd' => 'image/vnd.adobe.photoshop',
|
||||||
|
'ai' => 'application/postscript',
|
||||||
|
'eps' => 'application/postscript',
|
||||||
|
'indd' => 'application/x-indesign',
|
||||||
|
|
||||||
|
// CAD
|
||||||
|
'dwg' => 'image/vnd.dwg',
|
||||||
|
'dxf' => 'image/vnd.dxf',
|
||||||
|
'obj' => 'model/obj',
|
||||||
|
'stl' => 'model/stl',
|
||||||
|
|
||||||
|
// Мобильные приложения и исполняемые файлы
|
||||||
|
'apk' => 'application/vnd.android.package-archive',
|
||||||
|
'ipa' => 'application/octet-stream',
|
||||||
|
'exe' => 'application/x-msdownload',
|
||||||
|
'msi' => 'application/x-msi',
|
||||||
|
'deb' => 'application/vnd.debian.binary-package',
|
||||||
|
'rpm' => 'application/x-rpm',
|
||||||
|
'dmg' => 'application/x-apple-diskimage',
|
||||||
|
'pkg' => 'application/x-newton-compatible-pkg',
|
||||||
|
|
||||||
|
// Прочие
|
||||||
|
'torrent' => 'application/x-bittorrent',
|
||||||
|
'ics' => 'text/calendar',
|
||||||
|
'vcf' => 'text/vcard',
|
||||||
|
'gpx' => 'application/gpx+xml',
|
||||||
|
'kml' => 'application/vnd.google-earth.kml+xml',
|
||||||
|
'kmz' => 'application/vnd.google-earth.kmz'
|
||||||
|
];
|
||||||
|
|
||||||
|
$mimeType = $mimeTypes[$extension] ?? 'application/octet-stream';
|
||||||
|
|
||||||
|
header('Content-Type: ' . $mimeType);
|
||||||
|
header('Content-Length: ' . filesize($filepath));
|
||||||
|
header('Content-Disposition: inline; filename="' . $filename . '"');
|
||||||
|
header('Cache-Control: public, max-age=31536000'); // Кеш на год
|
||||||
|
|
||||||
|
readfile($filepath);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
echo '404 - Файл не найден';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка авторизации
|
||||||
if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') {
|
if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') {
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['password'] ?? '') === PASSWORD) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$csrf_token = $_POST['csrf_token'] ?? '';
|
||||||
|
|
||||||
|
// Проверяем CSRF токен
|
||||||
|
if (!verifyCSRFToken($csrf_token)) {
|
||||||
|
$error = 'Ошибка безопасности. Попробуйте еще раз.';
|
||||||
|
} elseif (password_verify($password, PASSWORD_HASH)) {
|
||||||
$_SESSION['logged_in'] = true;
|
$_SESSION['logged_in'] = true;
|
||||||
header("Location: index.php");
|
header("Location: index.php");
|
||||||
exit;
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Неверный пароль';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '<form method="post">
|
$csrf_token = generateCSRFToken();
|
||||||
<h3>Login</h3>
|
|
||||||
<input type="password" name="password" placeholder="Password">
|
echo '<!DOCTYPE html>
|
||||||
<button type="submit">Login</button>
|
<html>
|
||||||
</form>';
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Вход - Файловое хранилище</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-form">
|
||||||
|
<h3>Вход в систему</h3>
|
||||||
|
' . (isset($error) ? '<div class="notification error" style="display: block;">' . htmlspecialchars($error) . '</div>' : '') . '
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="csrf_token" value="' . htmlspecialchars($csrf_token) . '">
|
||||||
|
<input type="password" name="password" placeholder="Пароль" required>
|
||||||
|
<button type="submit" class="btn btn-primary" style="width: 100%;">Войти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаление
|
// Удаление файла
|
||||||
if (isset($_GET['delete'])) {
|
if (isset($_GET['delete']) && isset($_GET['csrf_token'])) {
|
||||||
$f = basename($_GET['delete']);
|
if (verifyCSRFToken($_GET['csrf_token'])) {
|
||||||
@unlink(UPLOAD_DIR . $f);
|
$filename = basename($_GET['delete']);
|
||||||
header("Location: index.php");
|
$filepath = UPLOAD_DIR . $filename;
|
||||||
|
|
||||||
|
if (file_exists($filepath)) {
|
||||||
|
@unlink($filepath);
|
||||||
|
$message = 'Файл удален успешно';
|
||||||
|
} else {
|
||||||
|
$error = 'Файл не найден';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = 'Ошибка безопасности';
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: index.php" . (isset($message) ? "?msg=" . urlencode($message) : (isset($error) ? "?err=" . urlencode($error) : "")));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Показ сообщений
|
||||||
|
$message = $_GET['msg'] ?? null;
|
||||||
|
$error = $_GET['err'] ?? null;
|
||||||
|
|
||||||
$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']);
|
$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']);
|
||||||
?><!DOCTYPE html>
|
$csrf_token = generateCSRFToken();
|
||||||
|
|
||||||
|
// Функция для форматирования размера файла уже в functions.php
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head><meta charset="utf-8"><title>Файловое хранилище</title></head>
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Файловое хранилище</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h3>Файловое хранилище</h3>
|
<div class="container">
|
||||||
<p><a href="?action=logout">Выйти</a></p>
|
<div class="header">
|
||||||
<input type="file" id="fileInput">
|
<h1>Файловое хранилище</h1>
|
||||||
<div id="progress"></div>
|
<a href="?action=logout" class="logout">Выйти</a>
|
||||||
<ul>
|
</div>
|
||||||
<?php foreach ($files as $file): $url = 'upload/' . rawurlencode($file); ?>
|
|
||||||
<li>
|
<div class="content">
|
||||||
<?= htmlspecialchars($file) ?>
|
<?php if ($message): ?>
|
||||||
[<a href="<?= $url ?>" target="_blank">Скачать</a>]
|
<div id="server-message" data-type="success" data-message="<?= htmlspecialchars($message) ?>"></div>
|
||||||
[<a href="?delete=<?= urlencode($file) ?>" onclick="return confirm('Удалить?')">Удалить</a>]
|
<?php endif; ?>
|
||||||
<button onclick="copyLink('<?= $url ?>')">Копировать ссылку</button>
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div id="server-message" data-type="error" data-message="<?= htmlspecialchars($error) ?>"></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div id="notification" class="notification"></div>
|
||||||
|
|
||||||
|
<div class="upload-area">
|
||||||
|
<div class="upload-zone" onclick="document.getElementById('fileInput').click()">
|
||||||
|
<input type="file" id="fileInput" class="file-input-hidden">
|
||||||
|
<div class="upload-text">
|
||||||
|
<strong>Нажмите для выбора файла</strong> или перетащите файл сюда
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-container" id="progressContainer">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" id="progressFill"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-text" id="progressText"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($files)): ?>
|
||||||
|
<div class="empty-state">
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<p>Файлы не загружены</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<ul class="files-list">
|
||||||
|
<?php foreach ($files as $file):
|
||||||
|
$filepath = UPLOAD_DIR . $file;
|
||||||
|
$filesize = file_exists($filepath) ? filesize($filepath) : 0;
|
||||||
|
$url = rawurlencode($file);
|
||||||
|
$delete_url = '?delete=' . urlencode($file) . '&csrf_token=' . urlencode($csrf_token);
|
||||||
|
?>
|
||||||
|
<li class="file-item">
|
||||||
|
<div class="file-info">
|
||||||
|
<div class="file-name"><?= htmlspecialchars($file) ?></div>
|
||||||
|
<div class="file-size"><?= formatFileSize($filesize) ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="file-actions">
|
||||||
|
<a href="<?= $url ?>" target="_blank" class="btn btn-secondary btn-small">Скачать</a>
|
||||||
|
<button onclick="copyLink('<?= htmlspecialchars($url) ?>')" class="btn btn-secondary btn-small">Копировать</button>
|
||||||
|
<a href="<?= $delete_url ?>" onclick="return confirm('Удалить файл <?= htmlspecialchars($file) ?>?')" class="btn btn-danger btn-small">Удалить</a>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function copyLink(link) {
|
const MAX_FILE_SIZE = <?= getMaxFileSize() ?>;
|
||||||
navigator.clipboard.writeText(location.origin + '/' + link).then(() => alert("Скопировано"));
|
const CHUNK_SIZE = <?= getChunkSize() ?>;
|
||||||
}
|
const ALLOWED_EXTENSIONS = <?= json_encode(ALLOWED_EXTENSIONS) ?>;
|
||||||
|
const CSRF_TOKEN = '<?= htmlspecialchars($csrf_token) ?>';
|
||||||
|
|
||||||
const input = document.getElementById('fileInput');
|
// Показать серверные сообщения при загрузке страницы
|
||||||
input.addEventListener('change', async () => {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const file = input.files[0];
|
const serverMessage = document.getElementById('server-message');
|
||||||
if (!file) return;
|
if (serverMessage) {
|
||||||
const chunkSize = 1024 * 1024; // 1MB
|
const type = serverMessage.getAttribute('data-type');
|
||||||
|
const message = serverMessage.getAttribute('data-message');
|
||||||
|
showNotification(message, type);
|
||||||
|
serverMessage.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag & Drop функциональность
|
||||||
|
const uploadZone = document.querySelector('.upload-zone');
|
||||||
|
|
||||||
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
|
uploadZone.addEventListener(eventName, preventDefaults, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function preventDefaults(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
['dragenter', 'dragover'].forEach(eventName => {
|
||||||
|
uploadZone.addEventListener(eventName, highlight, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(eventName => {
|
||||||
|
uploadZone.addEventListener(eventName, unhighlight, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function highlight(e) {
|
||||||
|
uploadZone.classList.add('dragover');
|
||||||
|
}
|
||||||
|
|
||||||
|
function unhighlight(e) {
|
||||||
|
uploadZone.classList.remove('dragover');
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadZone.addEventListener('drop', handleDrop, false);
|
||||||
|
|
||||||
|
function handleDrop(e) {
|
||||||
|
const dt = e.dataTransfer;
|
||||||
|
const files = dt.files;
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
document.getElementById('fileInput').files = files;
|
||||||
|
handleFileSelect(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик выбора файла через input
|
||||||
|
const input = document.getElementById('fileInput');
|
||||||
|
input.addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
console.log('Файл выбран:', file.name, 'Размер:', file.size);
|
||||||
|
handleFileSelect(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
const notification = document.getElementById('notification');
|
||||||
|
notification.textContent = message;
|
||||||
|
notification.className = `notification ${type}`;
|
||||||
|
notification.style.display = 'block';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyLink(link) {
|
||||||
|
navigator.clipboard.writeText(window.location.origin + '/' + link).then(() => {
|
||||||
|
showNotification("Ссылка скопирована в буфер обмена", 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
showNotification("Ошибка копирования ссылки", 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgress(current, total) {
|
||||||
|
const progressContainer = document.getElementById('progressContainer');
|
||||||
|
const progressFill = document.getElementById('progressFill');
|
||||||
|
const progressText = document.getElementById('progressText');
|
||||||
|
|
||||||
|
if (current === 0) {
|
||||||
|
progressContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentage = Math.round((current / total) * 100);
|
||||||
|
progressFill.style.width = percentage + '%';
|
||||||
|
progressText.textContent = `Загружено: ${current}/${total} (${percentage}%)`;
|
||||||
|
|
||||||
|
if (current === total) {
|
||||||
|
setTimeout(() => {
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateFile(file) {
|
||||||
|
console.log('Валидация файла:', file.name);
|
||||||
|
|
||||||
|
// Проверка размера
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
const maxSizeMB = Math.round(MAX_FILE_SIZE / 1024 / 1024);
|
||||||
|
showNotification(`Файл слишком большой. Максимальный размер: ${maxSizeMB} MB`, 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка расширения
|
||||||
|
const extension = file.name.split('.').pop().toLowerCase();
|
||||||
|
if (!ALLOWED_EXTENSIONS.includes(extension)) {
|
||||||
|
showNotification(`Недопустимый тип файла. Разрешены: ${ALLOWED_EXTENSIONS.join(', ')}`, 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка имени файла
|
||||||
|
if (!/^[a-zA-Z0-9а-яА-Я._\-\s()]+$/u.test(file.name)) {
|
||||||
|
showNotification('Имя файла содержит недопустимые символы', 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.name.length > 255) {
|
||||||
|
showNotification('Имя файла слишком длинное (максимум 255 символов)', 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Файл прошел валидацию');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileSelect(file) {
|
||||||
|
console.log('Обработка выбранного файла:', file ? file.name : 'null');
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
console.log('Файл не выбран');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateFile(file)) {
|
||||||
|
console.log('Файл не прошел валидацию');
|
||||||
|
document.getElementById('fileInput').value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Начинаем загрузку файла');
|
||||||
|
uploadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFile(file) {
|
||||||
|
const chunkSize = CHUNK_SIZE;
|
||||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
console.log(`Начинаем загрузку файла ${file.name}, размер: ${file.size} bytes, чанков: ${totalChunks}`);
|
||||||
|
|
||||||
|
showNotification('Начинаем загрузку файла...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Сбрасываем прогресс
|
||||||
|
updateProgress(0, totalChunks);
|
||||||
|
|
||||||
for (let i = 0; i < totalChunks; i++) {
|
for (let i = 0; i < totalChunks; i++) {
|
||||||
|
console.log(`Загружаем чанк ${i + 1} из ${totalChunks}`);
|
||||||
|
|
||||||
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('chunk', chunk);
|
formData.append('chunk', chunk);
|
||||||
formData.append('filename', file.name);
|
formData.append('filename', file.name);
|
||||||
formData.append('index', i);
|
formData.append('index', i);
|
||||||
formData.append('total', totalChunks);
|
formData.append('total', totalChunks);
|
||||||
|
formData.append('csrf_token', CSRF_TOKEN);
|
||||||
|
|
||||||
await fetch('upload_chunk.php', { method: 'POST', body: formData });
|
const response = await fetch('upload_chunk.php', {
|
||||||
document.getElementById('progress').innerText = `Загружено: ${i+1}/${totalChunks}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetch('merge_chunks.php', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: formData
|
||||||
body: JSON.stringify({ filename: file.name, total: totalChunks })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
alert('Готово!');
|
console.log(`Ответ сервера для чанка ${i + 1}:`, response.status, response.statusText);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error(`Ошибка загрузки чанка ${i + 1}:`, errorText);
|
||||||
|
throw new Error(`Ошибка загрузки чанка ${i + 1}: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(i + 1, totalChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Все чанки загружены, объединяем файл');
|
||||||
|
showNotification('Объединяем файл...', 'info');
|
||||||
|
|
||||||
|
const mergeResponse = await fetch('merge_chunks.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: file.name,
|
||||||
|
total: totalChunks,
|
||||||
|
csrf_token: CSRF_TOKEN
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Ответ сервера при объединении:', mergeResponse.status, mergeResponse.statusText);
|
||||||
|
|
||||||
|
if (!mergeResponse.ok) {
|
||||||
|
const errorText = await mergeResponse.text();
|
||||||
|
console.error('Ошибка при объединении файла:', errorText);
|
||||||
|
throw new Error(`Ошибка при объединении файла: ${mergeResponse.status} ${mergeResponse.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Файл успешно загружен!');
|
||||||
|
showNotification('Файл успешно загружен!', 'success');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
}, 1500);
|
||||||
</script>
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки:', error);
|
||||||
|
showNotification('Ошибка загрузки: ' + error.message, 'error');
|
||||||
|
document.getElementById('fileInput').value = '';
|
||||||
|
document.getElementById('progressContainer').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
195
merge_chunks.php
195
merge_chunks.php
@@ -1,21 +1,188 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once __DIR__ . '/config.php';
|
// Включаем отображение всех ошибок для отладки
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
session_start();
|
||||||
$filename = basename($data['filename']);
|
|
||||||
$total = intval($data['total']);
|
|
||||||
$tmpDir = __DIR__ . '/upload_chunks/' . $filename;
|
|
||||||
$finalPath = __DIR__ . '/upload/' . $filename;
|
|
||||||
|
|
||||||
$out = fopen($finalPath, 'w');
|
try {
|
||||||
for ($i = 0; $i < $total; $i++) {
|
error_log("=== MERGE CHUNKS START ===");
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
require_once __DIR__ . '/functions.php';
|
||||||
|
|
||||||
|
// Проверка авторизации
|
||||||
|
if (!isset($_SESSION['logged_in'])) {
|
||||||
|
error_log("ERROR: User not logged in");
|
||||||
|
http_response_code(403);
|
||||||
|
echo "Not authorized";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение и проверка JSON данных
|
||||||
|
$input = file_get_contents('php://input');
|
||||||
|
error_log("Input data: " . $input);
|
||||||
|
|
||||||
|
$data = json_decode($input, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
error_log("ERROR: JSON decode error: " . json_last_error_msg());
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Invalid JSON data";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Decoded data: " . print_r($data, true));
|
||||||
|
|
||||||
|
// Проверка CSRF токена
|
||||||
|
$csrf_token = $data['csrf_token'] ?? '';
|
||||||
|
if (!verifyCSRFToken($csrf_token)) {
|
||||||
|
error_log("ERROR: CSRF token verification failed");
|
||||||
|
http_response_code(403);
|
||||||
|
echo "CSRF token verification failed";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = getSafeFileName($data['filename'] ?? '');
|
||||||
|
$total = intval($data['total'] ?? 0);
|
||||||
|
|
||||||
|
error_log("Processing merge: filename='$filename', total=$total");
|
||||||
|
|
||||||
|
// Валидация данных
|
||||||
|
if (empty($filename) || $total <= 0) {
|
||||||
|
error_log("ERROR: Invalid parameters");
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Invalid parameters";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация имени файла
|
||||||
|
if (!validateFileName($filename)) {
|
||||||
|
error_log("ERROR: Invalid filename: $filename");
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Invalid filename";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpDir = CHUNK_DIR . $filename;
|
||||||
|
$finalPath = UPLOAD_DIR . $filename;
|
||||||
|
|
||||||
|
error_log("Chunk directory: $tmpDir");
|
||||||
|
error_log("Final path: $finalPath");
|
||||||
|
|
||||||
|
// Проверка существования директории с чанками
|
||||||
|
if (!file_exists($tmpDir)) {
|
||||||
|
error_log("ERROR: Chunk directory does not exist: $tmpDir");
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Chunk directory not found";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка всех чанков и подсчет общего размера
|
||||||
|
$totalSize = 0;
|
||||||
|
$missingChunks = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $total; $i++) {
|
||||||
$partPath = "$tmpDir/$i.part";
|
$partPath = "$tmpDir/$i.part";
|
||||||
$in = fopen($partPath, 'r');
|
if (!file_exists($partPath)) {
|
||||||
stream_copy_to_stream($in, $out);
|
$missingChunks[] = $i;
|
||||||
|
} else {
|
||||||
|
$chunkSize = filesize($partPath);
|
||||||
|
$totalSize += $chunkSize;
|
||||||
|
error_log("Chunk $i size: $chunkSize bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($missingChunks)) {
|
||||||
|
error_log("ERROR: Missing chunks: " . implode(', ', $missingChunks));
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Missing chunks: " . implode(', ', $missingChunks);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Total file size: $totalSize bytes");
|
||||||
|
error_log("Max allowed size: " . getMaxFileSize() . " bytes");
|
||||||
|
|
||||||
|
// Проверка размера файла
|
||||||
|
if ($totalSize > getMaxFileSize()) {
|
||||||
|
error_log("ERROR: File too large");
|
||||||
|
// Очистка чанков
|
||||||
|
for ($i = 0; $i < $total; $i++) {
|
||||||
|
@unlink("$tmpDir/$i.part");
|
||||||
|
}
|
||||||
|
@rmdir($tmpDir);
|
||||||
|
http_response_code(413); // Payload Too Large
|
||||||
|
echo "File too large";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка прав на запись в директорию загрузок
|
||||||
|
if (!is_writable(UPLOAD_DIR)) {
|
||||||
|
error_log("ERROR: Upload directory is not writable: " . UPLOAD_DIR);
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Upload directory is not writable";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Объединение чанков
|
||||||
|
error_log("Starting file merge");
|
||||||
|
$out = fopen($finalPath, 'wb');
|
||||||
|
if (!$out) {
|
||||||
|
error_log("ERROR: Cannot create final file: $finalPath");
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Cannot create final file";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $total; $i++) {
|
||||||
|
$partPath = "$tmpDir/$i.part";
|
||||||
|
error_log("Merging chunk $i from $partPath");
|
||||||
|
|
||||||
|
$in = fopen($partPath, 'rb');
|
||||||
|
if (!$in) {
|
||||||
|
error_log("ERROR: Cannot open chunk $i");
|
||||||
|
fclose($out);
|
||||||
|
@unlink($finalPath);
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Cannot open chunk $i";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$copied = stream_copy_to_stream($in, $out);
|
||||||
fclose($in);
|
fclose($in);
|
||||||
unlink($partPath);
|
|
||||||
|
error_log("Copied $copied bytes from chunk $i");
|
||||||
|
|
||||||
|
// Удаление чанка после успешного копирования
|
||||||
|
if (!unlink($partPath)) {
|
||||||
|
error_log("WARNING: Cannot delete chunk $i");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($out);
|
||||||
|
|
||||||
|
// Удаление директории чанков
|
||||||
|
if (!rmdir($tmpDir)) {
|
||||||
|
error_log("WARNING: Cannot delete chunk directory: $tmpDir");
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalSize = filesize($finalPath);
|
||||||
|
error_log("Final file size: $finalSize bytes");
|
||||||
|
error_log("SUCCESS: File merged successfully");
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
echo "OK";
|
||||||
|
|
||||||
|
error_log("=== MERGE CHUNKS END ===");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("EXCEPTION in merge_chunks.php: " . $e->getMessage());
|
||||||
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Server error: " . $e->getMessage();
|
||||||
|
} catch (Error $e) {
|
||||||
|
error_log("FATAL ERROR in merge_chunks.php: " . $e->getMessage());
|
||||||
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Fatal error: " . $e->getMessage();
|
||||||
}
|
}
|
||||||
fclose($out);
|
|
||||||
rmdir($tmpDir);
|
|
||||||
http_response_code(200);
|
|
||||||
|
|||||||
432
style.css
Normal file
432
style.css
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/* Сброс и базовые стили */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Цветовая палитра */
|
||||||
|
--primary: #2563eb;
|
||||||
|
--primary-hover: #1d4ed8;
|
||||||
|
--success: #059669;
|
||||||
|
--success-light: #d1fae5;
|
||||||
|
--error: #dc2626;
|
||||||
|
--error-light: #fee2e2;
|
||||||
|
--warning: #d97706;
|
||||||
|
--warning-light: #fef3c7;
|
||||||
|
|
||||||
|
/* Нейтральные цвета */
|
||||||
|
--gray-50: #f9fafb;
|
||||||
|
--gray-100: #f3f4f6;
|
||||||
|
--gray-200: #e5e7eb;
|
||||||
|
--gray-300: #d1d5db;
|
||||||
|
--gray-400: #9ca3af;
|
||||||
|
--gray-500: #6b7280;
|
||||||
|
--gray-600: #4b5563;
|
||||||
|
--gray-700: #374151;
|
||||||
|
--gray-800: #1f2937;
|
||||||
|
--gray-900: #111827;
|
||||||
|
|
||||||
|
/* Размеры и отступы */
|
||||||
|
--border-radius: 8px;
|
||||||
|
--border-radius-lg: 12px;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
|
|
||||||
|
/* Шрифты */
|
||||||
|
--font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-mono: 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-system);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--gray-700);
|
||||||
|
background: var(--gray-50);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контейнер */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Заголовок */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .logout {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .logout:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Основной контент */
|
||||||
|
.content {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Форма входа */
|
||||||
|
.login-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 4rem auto;
|
||||||
|
background: white;
|
||||||
|
padding: 2.5rem;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
color: var(--gray-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уведомления */
|
||||||
|
.notification {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: none;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.success {
|
||||||
|
background-color: var(--success-light);
|
||||||
|
border-color: var(--success);
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.error {
|
||||||
|
background-color: var(--error-light);
|
||||||
|
border-color: var(--error);
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.info {
|
||||||
|
background-color: #dbeafe;
|
||||||
|
border-color: var(--primary);
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Поля ввода */
|
||||||
|
input[type="password"],
|
||||||
|
input[type="file"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid var(--gray-200);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="password"]:focus,
|
||||||
|
input[type="file"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="password"] {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопки */
|
||||||
|
button,
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--gray-100);
|
||||||
|
color: var(--gray-700);
|
||||||
|
border: 1px solid var(--gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--gray-200);
|
||||||
|
border-color: var(--gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Область загрузки */
|
||||||
|
.upload-area {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone {
|
||||||
|
border: 2px dashed var(--gray-300);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: rgba(37, 99, 235, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone.dragover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: rgba(37, 99, 235, 0.05);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-hidden {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text {
|
||||||
|
color: var(--gray-500);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text strong {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прогресс загрузки */
|
||||||
|
.progress-container {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--gray-200);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--primary), var(--primary-hover));
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--gray-600);
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Список файлов */
|
||||||
|
.files-list {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--gray-200);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover {
|
||||||
|
border-color: var(--gray-300);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray-800);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--gray-500);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .btn {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пустое состояние */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
color: var(--gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state svg {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
body {
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header,
|
||||||
|
.content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions {
|
||||||
|
margin-left: 0;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Темные акценты для интерактивных элементов */
|
||||||
|
input[type="file"]::-webkit-file-upload-button {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: calc(var(--border-radius) - 2px);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"]::-webkit-file-upload-button:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимации */
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
120
upload_chunk.php
120
upload_chunk.php
@@ -1,8 +1,118 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$filename = basename($_POST['filename']);
|
// Включаем отображение всех ошибок для отладки
|
||||||
$index = intval($_POST['index']);
|
error_reporting(E_ALL);
|
||||||
$tmpDir = __DIR__ . '/upload_chunks/' . $filename;
|
ini_set('display_errors', 1);
|
||||||
if (!file_exists($tmpDir)) mkdir($tmpDir, 0777, true);
|
ini_set('log_errors', 1);
|
||||||
|
|
||||||
move_uploaded_file($_FILES['chunk']['tmp_name'], "$tmpDir/$index.part");
|
session_start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
error_log("=== UPLOAD CHUNK START ===");
|
||||||
|
error_log("POST data: " . print_r($_POST, true));
|
||||||
|
error_log("FILES data: " . print_r($_FILES, true));
|
||||||
|
error_log("Session data: " . print_r($_SESSION, true));
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
require_once __DIR__ . '/functions.php';
|
||||||
|
|
||||||
|
// Проверка авторизации
|
||||||
|
if (!isset($_SESSION['logged_in'])) {
|
||||||
|
error_log("ERROR: User not logged in");
|
||||||
|
http_response_code(403);
|
||||||
|
echo "Not authorized";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка CSRF токена
|
||||||
|
$csrf_token = $_POST['csrf_token'] ?? '';
|
||||||
|
error_log("CSRF token from POST: " . $csrf_token);
|
||||||
|
error_log("CSRF token from session: " . ($_SESSION['csrf_token'] ?? 'NOT SET'));
|
||||||
|
|
||||||
|
if (!verifyCSRFToken($csrf_token)) {
|
||||||
|
error_log("ERROR: CSRF token verification failed");
|
||||||
|
http_response_code(403);
|
||||||
|
echo "CSRF token verification failed";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка данных POST
|
||||||
|
if (!isset($_POST['filename']) || !isset($_POST['index'])) {
|
||||||
|
error_log("ERROR: Missing POST parameters");
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Missing parameters";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка загруженного файла
|
||||||
|
if (!isset($_FILES['chunk']) || $_FILES['chunk']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
error_log("ERROR: File upload error. Error code: " . ($_FILES['chunk']['error'] ?? 'NO_FILE'));
|
||||||
|
http_response_code(400);
|
||||||
|
echo "File upload error: " . ($_FILES['chunk']['error'] ?? 'NO_FILE');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = getSafeFileName($_POST['filename']);
|
||||||
|
$index = intval($_POST['index']);
|
||||||
|
|
||||||
|
error_log("Processing chunk: filename='$filename', index=$index");
|
||||||
|
|
||||||
|
// Валидация имени файла
|
||||||
|
if (!validateFileName($filename)) {
|
||||||
|
error_log("ERROR: Invalid filename: $filename");
|
||||||
|
http_response_code(400);
|
||||||
|
echo "Invalid filename";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание директории для чанков
|
||||||
|
$tmpDir = CHUNK_DIR . $filename;
|
||||||
|
error_log("Chunk directory: $tmpDir");
|
||||||
|
|
||||||
|
if (!file_exists($tmpDir)) {
|
||||||
|
if (!mkdir($tmpDir, 0755, true)) {
|
||||||
|
error_log("ERROR: Cannot create chunk directory: $tmpDir");
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Cannot create chunk directory";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
error_log("Created chunk directory: $tmpDir");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка прав на запись
|
||||||
|
if (!is_writable($tmpDir)) {
|
||||||
|
error_log("ERROR: Chunk directory is not writable: $tmpDir");
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Chunk directory is not writable";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunkPath = "$tmpDir/$index.part";
|
||||||
|
error_log("Chunk path: $chunkPath");
|
||||||
|
error_log("Temporary file: " . $_FILES['chunk']['tmp_name']);
|
||||||
|
error_log("Chunk size: " . $_FILES['chunk']['size']);
|
||||||
|
|
||||||
|
// Перемещение загруженного чанка
|
||||||
|
if (move_uploaded_file($_FILES['chunk']['tmp_name'], $chunkPath)) {
|
||||||
|
error_log("SUCCESS: Chunk saved to $chunkPath");
|
||||||
|
http_response_code(200);
|
||||||
|
echo "OK";
|
||||||
|
} else {
|
||||||
|
error_log("ERROR: Cannot move uploaded file to $chunkPath");
|
||||||
|
error_log("PHP error: " . error_get_last()['message'] ?? 'Unknown error');
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Cannot save chunk";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("=== UPLOAD CHUNK END ===");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("EXCEPTION in upload_chunk.php: " . $e->getMessage());
|
||||||
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Server error: " . $e->getMessage();
|
||||||
|
} catch (Error $e) {
|
||||||
|
error_log("FATAL ERROR in upload_chunk.php: " . $e->getMessage());
|
||||||
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Fatal error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user