папки с паролями

This commit is contained in:
2026-07-23 04:03:30 +08:00
parent 4d45b78a69
commit a644d3329c
2 changed files with 630 additions and 339 deletions

244
index.c
View File

@@ -12,6 +12,7 @@
#define MAX_ENTRIES 1000 #define MAX_ENTRIES 1000
#define TEMPLATE_PATH "/home/romkazvo/www/cgi-bin/template.html" #define TEMPLATE_PATH "/home/romkazvo/www/cgi-bin/template.html"
#define OUTPUT_BUFFER_SIZE 65536 #define OUTPUT_BUFFER_SIZE 65536
#define PASSWD_FILE "/home/romkazvo/www/cgi-bin/.htpasswd"
typedef struct { typedef struct {
char name[256]; char name[256];
@@ -20,6 +21,7 @@ typedef struct {
char icon[8]; char icon[8];
char encoded_path[1024]; char encoded_path[1024];
char file_url[1024]; char file_url[1024];
int is_protected;
} entry_t; } entry_t;
// Быстрый буферизированный вывод // Быстрый буферизированный вывод
@@ -58,6 +60,53 @@ void buffer_free(buffer_t *buf) {
free(buf->data); free(buf->data);
} }
// Функция проверки пароля для папки
int check_folder_password(const char *folder_name, const char *password) {
FILE *f = fopen(PASSWD_FILE, "r");
if (!f) return 1; // Если нет файла с паролями - доступ разрешен
char line[512];
while (fgets(line, sizeof(line), f)) {
// Убираем перевод строки
line[strcspn(line, "\r\n")] = 0;
char *colon = strchr(line, ':');
if (!colon) continue;
*colon = '\0';
char *folder = line;
char *pass = colon + 1;
if (strcmp(folder, folder_name) == 0) {
fclose(f);
return strcmp(pass, password) == 0;
}
}
fclose(f);
return 1; // Папка не найдена в списке - доступ разрешен
}
// Проверяет, защищена ли папка паролем
int is_folder_protected(const char *folder_name) {
FILE *f = fopen(PASSWD_FILE, "r");
if (!f) return 0;
char line[512];
while (fgets(line, sizeof(line), f)) {
line[strcspn(line, "\r\n")] = 0;
char *colon = strchr(line, ':');
if (!colon) continue;
*colon = '\0';
if (strcmp(line, folder_name) == 0) {
fclose(f);
return 1;
}
}
fclose(f);
return 0;
}
const char* get_file_icon(const char* filename) { const char* get_file_icon(const char* filename) {
const char *ext = strrchr(filename, '.'); const char *ext = strrchr(filename, '.');
if (!ext) return "📄"; if (!ext) return "📄";
@@ -90,21 +139,18 @@ const char* get_file_icon(const char* filename) {
return "📄"; return "📄";
} }
// Проверяет, можно ли открыть файл в браузере
int is_previewable(const char* filename) { int is_previewable(const char* filename) {
const char *ext = strrchr(filename, '.'); const char *ext = strrchr(filename, '.');
if (!ext) return 0; if (!ext) return 0;
ext++; ext++;
// Изображения
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 || if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 ||
strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0 || strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0 ||
strcasecmp(ext, "svg") == 0 || strcasecmp(ext, "ico") == 0) strcasecmp(ext, "svg") == 0 || strcasecmp(ext, "ico") == 0)
return 1; return 1;
// Текстовые файлы
if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 || if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 ||
strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0 || strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0 ||
strcasecmp(ext, "css") == 0 || strcasecmp(ext, "js") == 0 || strcasecmp(ext, "css") == 0 || strcasecmp(ext, "js") == 0 ||
@@ -112,17 +158,14 @@ int is_previewable(const char* filename) {
strcasecmp(ext, "csv") == 0) strcasecmp(ext, "csv") == 0)
return 1; return 1;
// PDF
if (strcasecmp(ext, "pdf") == 0) if (strcasecmp(ext, "pdf") == 0)
return 1; return 1;
// Аудио
if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 || if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 ||
strcasecmp(ext, "ogg") == 0 || strcasecmp(ext, "flac") == 0 || strcasecmp(ext, "ogg") == 0 || strcasecmp(ext, "flac") == 0 ||
strcasecmp(ext, "m4a") == 0 || strcasecmp(ext, "aac") == 0) strcasecmp(ext, "m4a") == 0 || strcasecmp(ext, "aac") == 0)
return 1; return 1;
// Видео
if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "webm") == 0 || if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "webm") == 0 ||
strcasecmp(ext, "ogv") == 0 || strcasecmp(ext, "mov") == 0 || strcasecmp(ext, "ogv") == 0 || strcasecmp(ext, "mov") == 0 ||
strcasecmp(ext, "avi") == 0 || strcasecmp(ext, "mkv") == 0) strcasecmp(ext, "avi") == 0 || strcasecmp(ext, "mkv") == 0)
@@ -254,6 +297,37 @@ void print_breadcrumb(buffer_t *buf, const char *display_path) {
buffer_append(buf, "\n</div>\n"); buffer_append(buf, "\n</div>\n");
} }
void print_password_form(buffer_t *buf, const char *folder_name) {
buffer_append(buf, "<div class=\"password-form-container\">\n");
buffer_append(buf, " <div class=\"password-form\">\n");
buffer_append(buf, " <div class=\"lock-icon\">🔒</div>\n");
buffer_append(buf, " <h2>Папка защищена паролем</h2>\n");
buffer_append(buf, " <p>Введите пароль для доступа к папке <strong>");
buffer_append(buf, folder_name);
buffer_append(buf, "</strong></p>\n");
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
char encoded_path[1024];
url_encode(folder_name, encoded_path, sizeof(encoded_path));
buffer_append(buf, encoded_path);
buffer_append(buf, "\">\n");
buffer_append(buf, " <input type=\"password\" name=\"password\" placeholder=\"Введите пароль\" required>\n");
buffer_append(buf, " <button type=\"submit\">Войти</button>\n");
buffer_append(buf, " </form>\n");
buffer_append(buf, " <p class=\"error-msg\">");
// Проверяем, была ли ошибка ввода пароля
char *query_string = getenv("QUERY_STRING");
if (query_string && strstr(query_string, "error=1")) {
buffer_append(buf, "❌ Неверный пароль! Попробуйте снова.");
}
buffer_append(buf, "</p>\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\" class=\"back-link\">← Вернуться на главную</a>\n");
buffer_append(buf, " </div>\n");
buffer_append(buf, "</div>\n");
}
void print_content(buffer_t *buf, const char *base_path, const char *display_path) { void print_content(buffer_t *buf, const char *base_path, const char *display_path) {
DIR *dir; DIR *dir;
struct dirent *entry; struct dirent *entry;
@@ -265,6 +339,46 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
int dir_count = 0, file_count = 0; int dir_count = 0, file_count = 0;
long long total_size = 0; long long total_size = 0;
// Проверяем, не защищена ли текущая папка паролем
char folder_name[256] = "";
if (display_path && display_path[0]) {
char *last_slash = strrchr(display_path, '/');
if (last_slash) {
strcpy(folder_name, last_slash + 1);
} else {
strcpy(folder_name, display_path);
}
// Проверяем пароль
char *query_string = getenv("QUERY_STRING");
char *password = NULL;
if (query_string) {
char *pass_start = strstr(query_string, "password=");
if (pass_start) {
pass_start += 9;
char *pass_end = strchr(pass_start, '&');
int pass_len = pass_end ? pass_end - pass_start : strlen(pass_start);
if (pass_len > 0 && pass_len < 256) {
char pass_buf[256];
strncpy(pass_buf, pass_start, pass_len);
pass_buf[pass_len] = '\0';
// URL декодируем пароль
char decoded_pass[256];
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
password = decoded_pass;
}
}
}
if (is_folder_protected(folder_name)) {
if (!password || !check_folder_password(folder_name, password)) {
// Показываем форму ввода пароля
print_password_form(buf, folder_name);
return;
}
}
}
dir = opendir(base_path); dir = opendir(base_path);
if (!dir) { if (!dir) {
char alt_path[1024]; char alt_path[1024];
@@ -298,6 +412,14 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
continue; continue;
if (strcmp(entry->d_name, "cgi-bin") == 0) if (strcmp(entry->d_name, "cgi-bin") == 0)
continue; continue;
// Скрываем файл с паролями (на всякий случай, если кто-то сможет получить доступ)
if (strcmp(entry->d_name, ".htpasswd") == 0)
continue;
if (strcmp(entry->d_name, ".htaccess") == 0)
continue;
// Скрываем все файлы, начинающиеся с точки
if (entry->d_name[0] == '.')
continue;
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name); snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
@@ -308,9 +430,16 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
if (S_ISDIR(file_stat.st_mode)) { if (S_ISDIR(file_stat.st_mode)) {
entries[entry_count].is_dir = 1; entries[entry_count].is_dir = 1;
entries[entry_count].size = 0; entries[entry_count].size = 0;
strcpy(entries[entry_count].icon, "📁");
// Предварительно кодируем путь для папок // Проверяем, защищена ли папка
if (is_folder_protected(entry->d_name)) {
strcpy(entries[entry_count].icon, "🔒");
entries[entry_count].is_protected = 1;
} else {
strcpy(entries[entry_count].icon, "📁");
entries[entry_count].is_protected = 0;
}
char new_path[2048]; char new_path[2048];
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, snprintf(new_path, sizeof(new_path), "%s%s%s", display_path,
display_path[0] ? "/" : "", entries[entry_count].name); display_path[0] ? "/" : "", entries[entry_count].name);
@@ -321,8 +450,8 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
entries[entry_count].is_dir = 0; entries[entry_count].is_dir = 0;
entries[entry_count].size = file_stat.st_size; entries[entry_count].size = file_stat.st_size;
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name)); strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
entries[entry_count].is_protected = 0;
// Предварительно формируем URL для файлов
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url),
"%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name); "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
@@ -349,7 +478,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
buffer_append(buf, "<ul>\n"); buffer_append(buf, "<ul>\n");
// Вывод папок с предварительно закодированными путями // Вывод папок
if (dir_count > 0) { if (dir_count > 0) {
char section_title[128]; char section_title[128];
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count); snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count);
@@ -358,24 +487,49 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
for (int i = 0; i < entry_count; i++) { for (int i = 0; i < entry_count; i++) {
if (entries[i].is_dir) { if (entries[i].is_dir) {
buffer_append(buf, " <li>\n"); buffer_append(buf, " <li>\n");
char dir_link[512];
snprintf(dir_link, sizeof(dir_link), if (entries[i].is_protected) {
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n", // Защищенная папка
entries[i].encoded_path); buffer_append(buf, " <div class=\"file-row\">\n");
buffer_append(buf, dir_link); buffer_append(buf, " <a class=\"dir-link protected-folder\" href=\"javascript:void(0)\" onclick=\"showPasswordForm('");
buffer_append(buf, " <span class=\"file-icon\">"); buffer_append(buf, entries[i].encoded_path);
buffer_append(buf, entries[i].icon); buffer_append(buf, "', '");
buffer_append(buf, "</span>\n"); buffer_append(buf, entries[i].name);
buffer_append(buf, " <span class=\"file-name\">"); buffer_append(buf, "')\">\n");
buffer_append(buf, entries[i].name); buffer_append(buf, " <span class=\"file-icon\">🔒</span>\n");
buffer_append(buf, "</span>\n"); buffer_append(buf, " <span class=\"file-name\">");
buffer_append(buf, " </a>\n"); buffer_append(buf, entries[i].name);
buffer_append(buf, " <span style=\"font-size:0.7rem;color:var(--text-muted);\">(защищена)</span></span>\n");
buffer_append(buf, " </a>\n");
buffer_append(buf, " <div class=\"file-controls\">\n");
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"showPasswordForm('");
buffer_append(buf, entries[i].encoded_path);
buffer_append(buf, "', '");
buffer_append(buf, entries[i].name);
buffer_append(buf, "')\" title=\"Ввести пароль\">🔑</button>\n");
buffer_append(buf, " </div>\n");
buffer_append(buf, " </div>\n");
} else {
// Обычная папка
char dir_link[512];
snprintf(dir_link, sizeof(dir_link),
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n",
entries[i].encoded_path);
buffer_append(buf, dir_link);
buffer_append(buf, " <span class=\"file-icon\">");
buffer_append(buf, entries[i].icon);
buffer_append(buf, "</span>\n");
buffer_append(buf, " <span class=\"file-name\">");
buffer_append(buf, entries[i].name);
buffer_append(buf, "</span>\n");
buffer_append(buf, " </a>\n");
}
buffer_append(buf, " </li>\n"); buffer_append(buf, " </li>\n");
} }
} }
} }
// Вывод файлов с предварительно сформированными URL // Вывод файлов
if (file_count > 0) { if (file_count > 0) {
char section_title[128]; char section_title[128];
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count); snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
@@ -389,7 +543,6 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
buffer_append(buf, " <li>\n"); buffer_append(buf, " <li>\n");
buffer_append(buf, " <div class=\"file-row\">\n"); buffer_append(buf, " <div class=\"file-row\">\n");
// Основная ссылка для скачивания
buffer_append(buf, " <a class=\"file-link\" href=\"/"); buffer_append(buf, " <a class=\"file-link\" href=\"/");
buffer_append(buf, entries[i].file_url); buffer_append(buf, entries[i].file_url);
buffer_append(buf, "\" download>\n"); buffer_append(buf, "\" download>\n");
@@ -403,7 +556,6 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
buffer_append(buf, " <div class=\"file-controls\">\n"); buffer_append(buf, " <div class=\"file-controls\">\n");
// Кнопка предпросмотра (если поддерживается)
if (is_previewable(entries[i].name)) { if (is_previewable(entries[i].name)) {
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('/"); buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('/");
buffer_append(buf, entries[i].file_url); buffer_append(buf, entries[i].file_url);
@@ -454,7 +606,6 @@ void print_template(const char *base_path, const char *display_path) {
} }
fclose(file); fclose(file);
// Один быстрый вывод вместо множества printf
fwrite(output.data, 1, output.size, stdout); fwrite(output.data, 1, output.size, stdout);
buffer_free(&output); buffer_free(&output);
} }
@@ -490,6 +641,47 @@ int main() {
safe_path_join(base_path, sizeof(base_path), "/home/romkazvo/www", safe_display_path); safe_path_join(base_path, sizeof(base_path), "/home/romkazvo/www", safe_display_path);
} }
} }
// Обработка неправильного пароля
if (strstr(query_string, "password=")) {
char *error = strstr(query_string, "error=1");
if (!error) {
// Проверяем пароль для текущей папки
char folder_name[256] = "";
if (display_path[0]) {
char *last_slash = strrchr(display_path, '/');
if (last_slash) {
strcpy(folder_name, last_slash + 1);
} else {
strcpy(folder_name, display_path);
}
if (is_folder_protected(folder_name)) {
char *pass_start = strstr(query_string, "password=");
if (pass_start) {
pass_start += 9;
char *pass_end = strchr(pass_start, '&');
int pass_len = pass_end ? pass_end - pass_start : strlen(pass_start);
if (pass_len > 0 && pass_len < 256) {
char pass_buf[256];
strncpy(pass_buf, pass_start, pass_len);
pass_buf[pass_len] = '\0';
char decoded_pass[256];
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
if (!check_folder_password(folder_name, decoded_pass)) {
// Неверный пароль - редирект с error=1
char encoded_path[1024];
url_encode(display_path, encoded_path, sizeof(encoded_path));
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
return 0;
}
}
}
}
}
}
}
} }
print_template(base_path, display_path); print_template(base_path, display_path);

725
template.html Executable file → Normal file
View File

@@ -1,313 +1,412 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
<title>Trashbox</title> <title>Trashbox</title>
<link rel="shortcut icon" href="/logo.png" type="image/png"> <link rel="shortcut icon" href="/logo.png" type="image/png">
<link rel="stylesheet" href="/cgi-bin/style.cgi"> <link rel="stylesheet" href="/cgi-bin/style.cgi">
<meta property="og:image" content="https://home.mashup.su/og.png" /> <meta property="og:image" content="https://home.mashup.su/og.png" />
<meta property="og:image:width" content="600" /> <meta property="og:image:width" content="600" />
<meta property="og:image:height" content="315" /> <meta property="og:image:height" content="315" />
<meta property="og:locale" content="ru_RU" /> <meta property="og:locale" content="ru_RU" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="Файлопомойка от RomkaZVO" /> <meta property="og:title" content="Файлопомойка от RomkaZVO" />
<meta property="og:description" content="Моя личная помоечка" /> <meta property="og:description" content="Моя личная помоечка" />
<meta property="og:url" content="https://home.mashup.su/" /> <meta property="og:url" content="https://home.mashup.su/" />
<meta property="og:site_name" content="Помойка от RomkaZVO" /> <meta property="og:site_name" content="Помойка от RomkaZVO" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Файлопомойка от RomkaZVO" /> <meta name="twitter:title" content="Файлопомойка от RomkaZVO" />
<meta name="twitter:description" content="Моя личная помоечка" /> <meta name="twitter:description" content="Моя личная помоечка" />
<meta name="twitter:image" content="https://home.mashup.su/og.png" /> <meta name="twitter:image" content="https://home.mashup.su/og.png" />
<meta name="description" content="Моя личная помоечка" /> <meta name="description" content="Моя личная помоечка" />
<meta name="keywords" content="RomkaZVO, mashup, mashup su, файлы, обмен, скачать, загрузить" /> <meta name="keywords" content="RomkaZVO, mashup, mashup su, файлы, обмен, скачать, загрузить" />
<script> <script>
function getCookie(name) { // Отключаем все alert на странице (на случай, если где-то закрались)
const value = `; ${document.cookie}`; window.alert = function() {
const parts = value.split(`; ${name}=`); console.log('Alert был заблокирован');
if (parts.length === 2) return parts.pop().split(';').shift(); };
}
function getCookie(name) {
(function() { const value = `; ${document.cookie}`;
const savedTheme = getCookie('theme'); const parts = value.split(`; ${name}=`);
if (savedTheme === 'dark') { if (parts.length === 2) return parts.pop().split(';').shift();
document.documentElement.classList.add('dark-theme'); }
}
})(); (function() {
</script> const savedTheme = getCookie('theme');
</head> if (savedTheme === 'dark') {
<body> document.documentElement.classList.add('dark-theme');
<button class="theme-toggle" onclick="toggleTheme()">🌙</button> }
})();
<div class="container"> </script>
<div class="header"> </head>
<h1>Trashbox</h1> <body>
<div style="margin-top: 0.5rem; opacity: 0.9; font-size: 1.1rem;"> <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
Моя личная помоечка
</div> <div class="container">
</div> <div class="header">
<h1>Trashbox</h1>
<div class="content"> <div style="margin-top: 0.5rem; opacity: 0.9; font-size: 1.1rem;">
<!--BREADCRUMB--> Моя личная помоечка
<!--CONTENT--> </div>
</div> </div>
</div>
<div class="content">
<footer class="footer"> <!--BREADCRUMB-->
<div class="footer-content"> <!--CONTENT-->
<span>Навайбкодил с любовью ❤️</span> </div>
<a href="https://romkazvo.ru" target="_blank" class="footer-link">RomkaZVO</a><br> </div>
Сделано при помощи BusyBox httpd и Nginx для SSL<br>
Боже, храни Китай партия за DeepSeek <footer class="footer">
</div> <div class="footer-content">
<span>Навайбкодил с любовью ❤️</span>
<div id="status" style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7; line-height: 1.4;"> <a href="https://romkazvo.ru" target="_blank" class="footer-link">RomkaZVO</a><br>
Загрузка статистики... Сделано при помощи BusyBox httpd и Nginx для SSL<br>
</div> Боже, храни Китай партия за DeepSeek
</div>
<script>
fetch('/cgi-bin/status.cgi') <div id="status" style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7; line-height: 1.4;">
.then(response => response.text()) Загрузка статистики...
.then(data => { </div>
document.getElementById('status').innerHTML = data;
}) <script>
.catch(err => { fetch('/cgi-bin/status.cgi')
document.getElementById('status').innerHTML = 'Ошибка загрузки статуса'; .then(response => response.text())
}); .then(data => {
</script> document.getElementById('status').innerHTML = data;
</footer> })
.catch(err => {
<button class="scroll-top" onclick="scrollToTop()"></button> document.getElementById('status').innerHTML = 'Ошибка загрузки статуса';
});
<div id="previewModal" class="preview-modal"> </script>
<div class="preview-content" id="previewContent"> </footer>
<div class="preview-header">
<h3 class="preview-title" id="previewTitle">Предпросмотр файла</h3> <button class="scroll-top" onclick="scrollToTop()"></button>
<button class="close-preview" onclick="closePreview()">×</button>
</div> <div id="previewModal" class="preview-modal">
<iframe id="previewFrame" class="preview-iframe" sandbox="allow-scripts allow-same-origin"></iframe> <div class="preview-content" id="previewContent">
</div> <div class="preview-header">
</div> <h3 class="preview-title" id="previewTitle">Предпросмотр файла</h3>
<button class="close-preview" onclick="closePreview()">×</button>
<script> </div>
function scrollToTop() { <iframe id="previewFrame" class="preview-iframe" sandbox="allow-scripts allow-same-origin"></iframe>
window.scrollTo({ </div>
top: 0, </div>
behavior: 'smooth'
}); <script>
} window.showPasswordForm = function(encodedPath, folderName) {
const modal = document.getElementById('previewModal');
window.addEventListener('scroll', function() { const content = document.getElementById('previewContent');
const scrollBtn = document.querySelector('.scroll-top'); const title = document.getElementById('previewTitle');
if (window.scrollY > 300) { const frame = document.getElementById('previewFrame');
scrollBtn.classList.add('visible');
} else { if (!modal || !content || !title || !frame) {
scrollBtn.classList.remove('visible'); console.error('Modal elements not found!');
} return;
}); }
function toggleTheme() { frame.style.display = 'none';
const isDark = document.body.classList.contains('dark-theme');
const newTheme = isDark ? 'light' : 'dark'; title.textContent = '🔒 Введите пароль для папки: ' + folderName;
document.body.classList.toggle('dark-theme'); const oldContainer = document.getElementById('passwordFormContainer');
document.documentElement.classList.toggle('dark-theme'); if (oldContainer) {
oldContainer.remove();
const button = document.querySelector('.theme-toggle'); }
button.textContent = isDark ? '🌙' : '☀️';
const container = document.createElement('div');
const date = new Date(); container.id = 'passwordFormContainer';
date.setFullYear(date.getFullYear() + 1); container.style.cssText = 'display:flex;justify-content:center;align-items:center;padding:2rem;flex-grow:1;min-height:300px;';
document.cookie = `theme=${newTheme}; expires=${date.toUTCString()}; path=/`;
} container.innerHTML = `
<div style="background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;padding:2rem;max-width:400px;width:100%;text-align:center;box-shadow:0 4px 20px rgba(0,0,0,0.1);">
function openPreview(fileUrl) { <div style="font-size:3rem;margin-bottom:1rem;">🔒</div>
const modal = document.getElementById('previewModal'); <h2 style="font-size:1.3rem;margin-bottom:0.5rem;color:var(--text-primary);">Защищенная папка</h2>
const frame = document.getElementById('previewFrame'); <p style="color:var(--text-secondary);margin-bottom:1.5rem;font-size:0.95rem;">
const content = document.getElementById('previewContent'); Введите пароль для доступа к <strong style="color:var(--text-primary);word-break:break-all;">${folderName}</strong>
const title = document.getElementById('previewTitle'); </p>
<form method="GET" action="/cgi-bin/index.cgi" onsubmit="return validatePasswordForm(this)">
const fileName = fileUrl.split('/').pop(); <input type="hidden" name="path" value="${encodedPath}">
title.textContent = `Предпросмотр: ${fileName}`; <input type="password" name="password" placeholder="Введите пароль" required style="width:100%;padding:0.75rem 1rem;border:2px solid var(--border-color);border-radius:8px;font-size:1rem;background:var(--bg-primary);color:var(--text-primary);transition:border-color 0.2s ease;box-sizing:border-box;">
<button type="submit" style="width:100%;padding:0.75rem;background:var(--accent-blue);color:white;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;transition:background 0.2s ease;margin-top:0.75rem;">Войти</button>
const fileExt = fileName.split('.').pop().toLowerCase(); </form>
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico']; <p class="error-msg" style="color:#dc3545;font-size:0.9rem;min-height:1.5rem;margin:0.5rem 0;"></p>
const textTypes = ['txt', 'md', 'html', 'htm', 'css', 'js', 'json', 'xml', 'csv']; <button onclick="closePreview()" style="background:transparent;border:none;color:var(--text-secondary);cursor:pointer;font-size:0.9rem;margin-top:0.5rem;">✖ Закрыть</button>
const audioTypes = ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac']; </div>
const videoTypes = ['mp4', 'webm', 'ogv', 'mov', 'avi', 'mkv']; `;
const isPDF = fileExt === 'pdf';
content.appendChild(container);
frame.style.display = 'block';
frame.src = ''; content.style.width = '500px';
content.style.height = 'auto';
let imgContainer = document.getElementById('previewImageContainer'); content.style.maxWidth = '90vw';
if (!imgContainer) { content.style.maxHeight = 'auto';
imgContainer = document.createElement('div'); content.style.margin = 'auto';
imgContainer.id = 'previewImageContainer'; content.style.position = 'absolute';
imgContainer.className = 'preview-image-container'; content.style.left = '50%';
content.appendChild(imgContainer); content.style.top = '50%';
} content.style.transform = 'translate(-50%, -50%)';
imgContainer.style.display = 'none';
imgContainer.innerHTML = ''; modal.style.display = 'block';
document.body.style.overflow = 'hidden';
if (imageTypes.includes(fileExt)) {
frame.style.display = 'none'; setTimeout(() => {
imgContainer.style.display = 'flex'; const input = container.querySelector('input[type="password"]');
if (input) input.focus();
const img = document.createElement('img'); }, 100);
img.src = fileUrl; };
img.alt = fileName;
img.className = 'preview-image'; window.validatePasswordForm = function(form) {
const password = form.querySelector('input[type="password"]').value;
img.onload = function() { const errorMsg = form.parentElement.querySelector('.error-msg');
adjustModalForImage(this); if (!password || password.length < 1) {
}; if (errorMsg) {
errorMsg.textContent = '❌ Пожалуйста, введите пароль';
img.onerror = function() { }
console.error('Failed to load image:', fileUrl); return false;
frame.style.display = 'block'; }
imgContainer.style.display = 'none'; return true;
frame.src = fileUrl; };
};
window.scrollToTop = function() {
imgContainer.appendChild(img); window.scrollTo({
top: 0,
content.style.width = '90vw'; behavior: 'smooth'
content.style.height = '90vh'; });
content.style.maxWidth = 'none'; };
content.style.maxHeight = 'none';
window.addEventListener('scroll', function() {
} else { const scrollBtn = document.querySelector('.scroll-top');
frame.style.display = 'block'; if (window.scrollY > 300) {
imgContainer.style.display = 'none'; scrollBtn.classList.add('visible');
} else {
if (textTypes.includes(fileExt)) { scrollBtn.classList.remove('visible');
content.style.width = '80vw'; }
content.style.height = '70vh'; });
content.style.maxWidth = '800px';
content.style.maxHeight = '600px'; window.toggleTheme = function() {
} else if (audioTypes.includes(fileExt)) { const isDark = document.body.classList.contains('dark-theme');
content.style.width = '500px'; const newTheme = isDark ? 'light' : 'dark';
content.style.height = '300px';
content.style.maxWidth = '90vw'; document.body.classList.toggle('dark-theme');
content.style.maxHeight = '400px'; document.documentElement.classList.toggle('dark-theme');
} else if (videoTypes.includes(fileExt)) {
content.style.width = '90vw'; const button = document.querySelector('.theme-toggle');
content.style.height = '80vh'; button.textContent = isDark ? '🌙' : '☀️';
content.style.maxWidth = '1200px';
content.style.maxHeight = '800px'; const date = new Date();
} else if (isPDF) { date.setFullYear(date.getFullYear() + 1);
content.style.width = '90vw'; document.cookie = 'theme=' + newTheme + '; expires=' + date.toUTCString() + '; path=/';
content.style.height = '90vh'; };
content.style.maxWidth = '1200px';
content.style.maxHeight = '900px'; window.openPreview = function(fileUrl) {
frame.classList.add('pdf'); const modal = document.getElementById('previewModal');
} else { const frame = document.getElementById('previewFrame');
content.style.width = '80vw'; const content = document.getElementById('previewContent');
content.style.height = '80vh'; const title = document.getElementById('previewTitle');
content.style.maxWidth = '1000px';
content.style.maxHeight = '800px'; const oldContainer = document.getElementById('passwordFormContainer');
} if (oldContainer) {
oldContainer.remove();
frame.src = fileUrl; }
}
frame.style.display = 'block';
modal.style.display = 'block';
document.body.style.overflow = 'hidden'; const fileName = fileUrl.split('/').pop();
} title.textContent = 'Предпросмотр: ' + fileName;
const fileExt = fileName.split('.').pop().toLowerCase();
function adjustModalForImage(img) { const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico'];
const content = document.getElementById('previewContent'); const textTypes = ['txt', 'md', 'html', 'htm', 'css', 'js', 'json', 'xml', 'csv'];
const maxWidth = window.innerWidth * 0.95; const audioTypes = ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac'];
const maxHeight = window.innerHeight * 0.95; const videoTypes = ['mp4', 'webm', 'ogv', 'mov', 'avi', 'mkv'];
const isPDF = fileExt === 'pdf';
const imgWidth = img.naturalWidth;
const imgHeight = img.naturalHeight; frame.src = '';
console.log(`Image size: ${imgWidth}x${imgHeight}, Screen limits: ${maxWidth}x${maxHeight}`); let imgContainer = document.getElementById('previewImageContainer');
if (!imgContainer) {
let scale = 1; imgContainer = document.createElement('div');
imgContainer.id = 'previewImageContainer';
if (imgWidth > maxWidth) { imgContainer.className = 'preview-image-container';
scale = Math.min(scale, maxWidth / imgWidth); content.appendChild(imgContainer);
} }
if (imgHeight > maxHeight) { imgContainer.style.display = 'none';
scale = Math.min(scale, maxHeight / imgHeight); imgContainer.innerHTML = '';
}
if (imageTypes.includes(fileExt)) {
const displayWidth = imgWidth * scale; frame.style.display = 'none';
const displayHeight = imgHeight * scale; imgContainer.style.display = 'flex';
console.log(`Display size: ${displayWidth}x${displayHeight}, Scale: ${scale}`); const img = document.createElement('img');
img.src = fileUrl;
content.style.width = (displayWidth + 20) + 'px'; img.alt = fileName;
content.style.height = (displayHeight + 80) + 'px'; img.className = 'preview-image';
content.style.margin = 'auto'; img.onload = function() {
content.style.position = 'absolute'; adjustModalForImage(this);
content.style.left = '50%'; };
content.style.top = '50%';
content.style.transform = 'translate(-50%, -50%)'; img.onerror = function() {
} console.error('Failed to load image:', fileUrl);
frame.style.display = 'block';
function closePreview() { imgContainer.style.display = 'none';
const modal = document.getElementById('previewModal'); frame.src = fileUrl;
const frame = document.getElementById('previewFrame'); };
const content = document.getElementById('previewContent');
imgContainer.appendChild(img);
modal.style.display = 'none';
frame.src = ''; content.style.width = '90vw';
frame.classList.remove('pdf'); content.style.height = '90vh';
content.style.maxWidth = 'none';
content.style.width = ''; content.style.maxHeight = 'none';
content.style.height = ''; content.style.margin = '2% auto';
content.style.maxWidth = ''; content.style.position = 'relative';
content.style.maxHeight = ''; content.style.left = 'auto';
content.style.margin = ''; content.style.top = 'auto';
content.style.position = ''; content.style.transform = 'none';
content.style.left = '';
content.style.top = ''; } else {
content.style.transform = ''; frame.style.display = 'block';
imgContainer.style.display = 'none';
const imgContainer = document.getElementById('previewImageContainer');
if (imgContainer) { if (textTypes.includes(fileExt)) {
imgContainer.style.display = 'none'; content.style.width = '80vw';
imgContainer.innerHTML = ''; content.style.height = '70vh';
} content.style.maxWidth = '800px';
content.style.maxHeight = '600px';
document.body.style.overflow = 'auto'; } else if (audioTypes.includes(fileExt)) {
} content.style.width = '500px';
content.style.height = '300px';
document.getElementById('previewModal').addEventListener('click', function(e) { content.style.maxWidth = '90vw';
if (e.target === this) { content.style.maxHeight = '400px';
closePreview(); } else if (videoTypes.includes(fileExt)) {
} content.style.width = '90vw';
}); content.style.height = '80vh';
content.style.maxWidth = '1200px';
document.addEventListener('keydown', function(e) { content.style.maxHeight = '800px';
if (e.key === 'Escape') { } else if (isPDF) {
closePreview(); content.style.width = '90vw';
} content.style.height = '90vh';
}); content.style.maxWidth = '1200px';
content.style.maxHeight = '900px';
document.addEventListener('DOMContentLoaded', function() { frame.classList.add('pdf');
const savedTheme = getCookie('theme'); } else {
const button = document.querySelector('.theme-toggle'); content.style.width = '80vw';
content.style.height = '80vh';
if (savedTheme === 'dark') { content.style.maxWidth = '1000px';
document.body.classList.add('dark-theme'); content.style.maxHeight = '800px';
button.textContent = '☀️'; }
} else {
document.body.classList.remove('dark-theme'); content.style.margin = '2% auto';
button.textContent = '🌙'; content.style.position = 'relative';
} content.style.left = 'auto';
content.style.top = 'auto';
document.body.classList.add('loaded'); content.style.transform = 'none';
});
frame.src = fileUrl;
document.addEventListener('touchstart', function() {}, { passive: true }); }
</script>
</body> modal.style.display = 'block';
</html> document.body.style.overflow = 'hidden';
};
window.adjustModalForImage = function(img) {
const content = document.getElementById('previewContent');
const maxWidth = window.innerWidth * 0.95;
const maxHeight = window.innerHeight * 0.95;
const imgWidth = img.naturalWidth;
const imgHeight = img.naturalHeight;
let scale = 1;
if (imgWidth > maxWidth) {
scale = Math.min(scale, maxWidth / imgWidth);
}
if (imgHeight > maxHeight) {
scale = Math.min(scale, maxHeight / imgHeight);
}
const displayWidth = imgWidth * scale;
const displayHeight = imgHeight * scale;
content.style.width = (displayWidth + 20) + 'px';
content.style.height = (displayHeight + 80) + 'px';
content.style.margin = 'auto';
content.style.position = 'absolute';
content.style.left = '50%';
content.style.top = '50%';
content.style.transform = 'translate(-50%, -50%)';
};
window.closePreview = function() {
const modal = document.getElementById('previewModal');
const frame = document.getElementById('previewFrame');
const content = document.getElementById('previewContent');
modal.style.display = 'none';
frame.src = '';
frame.classList.remove('pdf');
frame.style.display = 'block';
const oldContainer = document.getElementById('passwordFormContainer');
if (oldContainer) {
oldContainer.remove();
}
content.style.width = '';
content.style.height = '';
content.style.maxWidth = '';
content.style.maxHeight = '';
content.style.margin = '';
content.style.position = '';
content.style.left = '';
content.style.top = '';
content.style.transform = '';
const imgContainer = document.getElementById('previewImageContainer');
if (imgContainer) {
imgContainer.style.display = 'none';
imgContainer.innerHTML = '';
}
const title = document.getElementById('previewTitle');
title.textContent = 'Предпросмотр файла';
document.body.style.overflow = 'auto';
};
document.getElementById('previewModal').addEventListener('click', function(e) {
if (e.target === this) {
closePreview();
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closePreview();
}
});
document.addEventListener('DOMContentLoaded', function() {
const savedTheme = getCookie('theme');
const button = document.querySelector('.theme-toggle');
if (savedTheme === 'dark') {
document.body.classList.add('dark-theme');
button.textContent = '☀️';
} else {
document.body.classList.remove('dark-theme');
button.textContent = '🌙';
}
document.body.classList.add('loaded');
});
document.addEventListener('touchstart', function() {}, { passive: true });
</script>
</body>
</html>