diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..112c386 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +upload/ +upload_chunks/ diff --git a/config.php b/config.php index e71c7bc..2af42b6 100644 --- a/config.php +++ b/config.php @@ -1,4 +1,72 @@ 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("======================"); +} diff --git a/index.php b/index.php index 719f9c9..4305143 100644 --- a/index.php +++ b/index.php @@ -2,86 +2,571 @@ session_start(); require_once __DIR__ . '/config.php'; +require_once __DIR__ . '/functions.php'; -if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR); -if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR); +if (!file_exists(UPLOAD_DIR)) mkdir(UPLOAD_DIR, 0755, true); +if (!file_exists(CHUNK_DIR)) mkdir(CHUNK_DIR, 0755, true); -if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') { - if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['password'] ?? '') === PASSWORD) { - $_SESSION['logged_in'] = true; - header("Location: index.php"); +// Обработка запросов файлов напрямую из корня +$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; } - - echo '
-

Login

- - -
'; - exit; } -// Удаление -if (isset($_GET['delete'])) { - $f = basename($_GET['delete']); - @unlink(UPLOAD_DIR . $f); - header("Location: index.php"); - exit; -} +// Проверка авторизации +if (!isset($_SESSION['logged_in']) || ($_GET['action'] ?? '') === 'logout') { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $password = $_POST['password'] ?? ''; + $csrf_token = $_POST['csrf_token'] ?? ''; -$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']); -?> - -Файловое хранилище - -

Файловое хранилище

-

Выйти

- -
- - - + echo ' + + + + + Вход - Файловое хранилище + + + +
+

Вход в систему

+ ' . (isset($error) ? '
' . htmlspecialchars($error) . '
' : '') . ' +
+ + + +
+
+ + '; + exit; +} + +// Удаление файла +if (isset($_GET['delete']) && isset($_GET['csrf_token'])) { + if (verifyCSRFToken($_GET['csrf_token'])) { + $filename = basename($_GET['delete']); + $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; +} + +// Показ сообщений +$message = $_GET['msg'] ?? null; +$error = $_GET['err'] ?? null; + +$files = array_diff(scandir(UPLOAD_DIR), ['.', '..']); +$csrf_token = generateCSRFToken(); + +// Функция для форматирования размера файла уже в functions.php +?> + + + + + + + Файловое хранилище + + + + +
+
+

Файловое хранилище

+ Выйти +
+ +
+ +
+ + + +
+ + +
+ +
+
+ +
+ Нажмите для выбора файла или перетащите файл сюда +
+
+ +
+
+
+
+
+
+
+ + +
+ + + +

Файлы не загружены

+
+ + + +
+
+ + + diff --git a/merge_chunks.php b/merge_chunks.php index d57f5e4..2803ec4 100644 --- a/merge_chunks.php +++ b/merge_chunks.php @@ -1,21 +1,188 @@ 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); + + 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); diff --git a/style.css b/style.css new file mode 100644 index 0000000..98bbd56 --- /dev/null +++ b/style.css @@ -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; } +} diff --git a/upload_chunk.php b/upload_chunk.php index b59154d..e3c1216 100644 --- a/upload_chunk.php +++ b/upload_chunk.php @@ -1,8 +1,118 @@ 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(); +}