сортировка, переделана авторизация, изменен дизайн
This commit is contained in:
184
README.md
184
README.md
@@ -1,56 +1,170 @@
|
||||
# 🗂️ Trashbox - Минималистичный файловый сервер
|
||||
# Trashbox — Минималистичный CGI-файловый сервер
|
||||
|
||||
Легковесный файлообменник на чистом C для Linux/Debian. Работает через связку **Nginx (Reverse Proxy)** + **BusyBox `httpd` (CGI Backend)**.
|
||||
> **Примечания:**<br> 1) Nginx опционален, нужен только если необходимен Reverse Proxy, например, для SSL.<br>
|
||||
> 2) Зачем? Хотелось что-то легковесного, но сам я не погромист, потому был применён вайбкод подход, делал вместе с DeepSeek. Вдохновленно [этим](https://github.com/metalx1000/Directory-Index-for-httpd) проектом, но там есть нюанс, сурсы закрыты, а что-то такое от рута пускать такое себе.<br>
|
||||
> 3) [Здесь](https://home.mashup.su) можно потыкать и посмотреть как работает.<br>
|
||||
> 4) Нейрокод вряд-ли качественный, да и конечно же без багов никуда, есть issue, PR, кому это инетерсно, конечно.<br>
|
||||
> 5) Активно (не очень) какаю в репу в своем [GitLab](https://git.mashup.su/RomkaZVO/Trashbox), а тут скорее просто результат.
|
||||
|
||||
## Скриншоты:
|
||||
### Главная страница (светлая тема)
|
||||
|
||||
https://home.mashup.su/trashpics/1.png
|
||||
### Тёмная тема
|
||||
|
||||
https://home.mashup.su/trashpics/2.png
|
||||
### Мобильная версия
|
||||
|
||||
https://home.mashup.su/trashpics/3.png
|
||||
### Форма пароля
|
||||
|
||||
https://home.mashup.su/trashpics/4.png
|
||||
## Полный список возможностей
|
||||
|
||||
### Авторизация и безопасность
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Пароль на папки** | Защита отдельных директорий через `.htpasswd`. |
|
||||
| **2** | **POST-форма авторизации** | Безопасная передача пароля. |
|
||||
| **3** | **Токены сессий** | Уникальные токены для доступа к защищённым разделам. |
|
||||
| **4** | **Токен в localStorage** | Сохранение авторизации в браузере между переходами. |
|
||||
| **5** | **Время жизни сессии** | Токен валиден **1 час**, после чего автоматически удаляется на сервере. |
|
||||
| **6** | **Защита от брутфорса** | Лимит в **5 неудачных попыток** ввода пароля. |
|
||||
| **7** | **Временный бан** | Блокировка доступа на **15 минут** при превышении лимита попыток. |
|
||||
|
||||
### Навигация и работа с файлами
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Навигация** | Удобная (вроде) навигационная цепочка. |
|
||||
| **2** | **Просмотр директорий** | Наглядное отображение полного содержимого папок. |
|
||||
| **3** | **Гибкая сортировка** | Сортировка по имени, размеру и дате. |
|
||||
| **4** | **Поиск файлов** | Рекурсивный поиск по имени внутри текущей директории. |
|
||||
| **5** | **Скачивание файлов** | Прямая выдача файлов. |
|
||||
| **6** | **Предпросмотр** | Модальное окно для просмотра картинок, видео, аудио, PDF и текста. |
|
||||
| **7** | **Копирование ссылок** | Быстрое копирование чистой прямой ссылки на файл или папку. |
|
||||
| **8** | **Иконки типов файлов** | Наглядные эмодзи-иконки для разграничения форматов. |
|
||||
| **9** | **Подсчет размера папок** | Рекурсивный расчет и отображение общего объема папки. |
|
||||
| **10** | **Сводная статистика** | Подсчет количества файлов, папок и их суммарного размера. |
|
||||
|
||||
### Интерфейс и UX
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Адаптивный дизайн** | Полная оптимизация под ПК, планшеты и смартфоны. |
|
||||
| **2** | **Тёмная / Светлая тема** | Переключение оформления с сохранением выбора. |
|
||||
| **3** | **Сохранение контекста** | Навигация по хлебным крошкам без потери авторизации. |
|
||||
| **4** | **Единая высота тулбара** | Одинаковый размер и стиль для элементов поиска и сортировки. |
|
||||
| **5** | **Увеличенный UI на мобильных устройствах** | Крупные кнопки и элементы для удобного нажатия пальцами. |
|
||||
| **6** | **Кнопка «Наверх»** | Быстрая прокрутка к началу страницы. |
|
||||
| **7** | **Фиксированная сетка** | Фиксированная ширина иконок без «прыжков» верстки. |
|
||||
| **8** | **Папки жирным шрифтом** | Визуальное отличие папок от файлов. |
|
||||
| **9** | **Модальная авторизация** | Красивое окно запроса пароля при входе в закрытую папку. |
|
||||
|
||||
### Мониторинг и логи
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Статус сервера** | Показывает память BusyBox/Nginx и нагрузку CPU. |
|
||||
| **2** | **Асинхронные метрики** | Подгрузка статистики без блокировки основного UI и сессии. |
|
||||
|
||||
### Технические особенности
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Чистый C** | Без PHP/Python/Node.js, максимум производительности при минимуме зависимостей. |
|
||||
| **2** | **Универсальный CGI** | Работает через CGI на любом веб-сервере. |
|
||||
| **3** | **Оптимизация под BusyBox** | Идеально подходит для встраиваемых систем и домашних серверов. |
|
||||
| **4** | **~2 МБ RAM** | Минимальное потребление оперативной памяти. |
|
||||
| **5** | **Хранение токенов в `/tmp`** | Автоматическая очистка сессий при перезагрузке системы. |
|
||||
| **6** | **Конфигурация `config.ini`** | Гибкая настройка путей и параметров без пересборки. |
|
||||
|
||||
### Оптимизация для мобильных устройств
|
||||
| № | Особенность| Примечание |
|
||||
| :-: | :--- | :--- |
|
||||
| **1** | **Крупные кнопки** | Размер тач-зон 38–44px для удобства клика пальцем. |
|
||||
| **2** | **Скрытие даты** | На маленьких экранах дата скрыта, отображается только размер. |
|
||||
| **3** | **Увеличенные иконки** | Значки 32px вместо мелких 20px. |
|
||||
---
|
||||
|
||||
## Установка зависимостей
|
||||
|
||||
### Установка зависимостей
|
||||
``` bash
|
||||
sudo apt update
|
||||
sudo apt install nginx busybox gcc
|
||||
sudo apt install nginx busybox gcc make
|
||||
```
|
||||
### Настройка структуры
|
||||
```bash
|
||||
mkdir -p /home/romkazvo/www/cgi-bin
|
||||
cd /home/romkazvo/www/cgi-bin
|
||||
git clone https://git.mashup.su/RomkaZVO/Trashbox.git
|
||||
---
|
||||
|
||||
## Настройка структуры и загрузка
|
||||
|
||||
``` bash
|
||||
mkdir -p /patch/to/www/cgi-bin
|
||||
cd /patch/to/www/cgi-bin
|
||||
git clone https://git.mashup.su/RomkaZVO/Trashbox.git .
|
||||
```
|
||||
### Структура
|
||||
---
|
||||
|
||||
## Структура
|
||||
|
||||
``` bash
|
||||
cgi-bin/
|
||||
├── Makefile
|
||||
├── config.ini
|
||||
├── template.html
|
||||
├── style.css
|
||||
├── .htpasswd
|
||||
├── .htpasswd # Файл с паролями для защищенных папок
|
||||
├── src/
|
||||
│ ├── main.c
|
||||
│ ├── config.c / config.h
|
||||
│ ├── auth.c / auth.h
|
||||
│ ├── fs.c / fs.h
|
||||
│ ├── render.c / render.h
|
||||
│ ├── utils.c / utils.h
|
||||
│ ├── style.c
|
||||
│ └── status.c
|
||||
├── index.cgi
|
||||
├── style.cgi
|
||||
└── status.cgi
|
||||
403 и 404 опционально в папку уровнем выше, чем cgi-bin
|
||||
│ ├── main.c # Точка входа главного CGI-скрипта
|
||||
│ ├── config.c / .h # Парсинг конфигурации (config.ini)
|
||||
│ ├── auth.c / .h # Авторизация, сессии и защита от брутфорса
|
||||
│ ├── fs.c / .h # Навигация по файловой системе и операции
|
||||
│ ├── render.c / .h # Шаблонизатор и генерация HTML
|
||||
│ ├── utils.c / .h # Вспомогательные функции (URL encoding, escape, форматирование)
|
||||
│ ├── style.c # Выдача и кэширование CSS
|
||||
│ └── status.c # Мониторинг системных ресурсов
|
||||
├── index.cgi # Бинарник списка файлов / навигации
|
||||
├── style.cgi # Бинарник отдачи стилей
|
||||
└── status.cgi # Бинарник системной статистики
|
||||
```
|
||||
### Компиляция CGI
|
||||
```bash
|
||||
> **Примечание:** Кастомные страницы ошибок 403.html и 404.html опционально размещаются на уровень выше cgi-bin - в /patch/to/www/.
|
||||
|
||||
---
|
||||
|
||||
## Компиляция и установка
|
||||
``` bash
|
||||
cd src
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
### Конфигурация Nginx
|
||||
Файл конфигурации `/etc/nginx/sites-available/trashbox`:<br>
|
||||
Образец здесь [тык](https://git.mashup.su/RomkaZVO/Trashbox/src/branch/main/busybox_nginx_reverse.txt)
|
||||
---
|
||||
|
||||
### Запуск BusyBox
|
||||
```bash
|
||||
busybox httpd -p 127.0.0.1:8050 -h /home/romkazvo/www
|
||||
## Конфигурация Nginx
|
||||
|
||||
Создайте файл конфигурации /etc/nginx/sites-available/service.
|
||||
Образец конфигурации доступен по ссылке:
|
||||
[Конфигурация BusyBox + Nginx](https://git.mashup.su/RomkaZVO/Trashbox/src/branch/main/busybox_nginx_reverse.txt)
|
||||
|
||||
Активация виртуального хоста:
|
||||
``` bash
|
||||
sudo ln -s /etc/nginx/sites-available/service /etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
### Папки с паролями
|
||||
Создается папка, а для создания пароля нужно:
|
||||
```bash
|
||||
cat > /home/romkazvo/www/cgi-bin/.htpasswd << EOF
|
||||
secret:mysecretpassword
|
||||
---
|
||||
|
||||
## Запуск BusyBox
|
||||
|
||||
Запуск фонового CGI-сервера:
|
||||
``` bash
|
||||
busybox httpd -p 127.0.0.1:8050 -h /patch/to/www
|
||||
```
|
||||
---
|
||||
|
||||
## Настройка папок с паролями (.htpasswd)
|
||||
|
||||
Папка создается обычным образом в файловой системе. Чтобы задать для нее пароль, добавьте запись в файл .htpasswd:
|
||||
``` bash
|
||||
cat >> /patch/to/www/cgi-bin/.htpasswd << EOF
|
||||
secret_folder:mysecretpassword
|
||||
EOF
|
||||
```
|
||||
---
|
||||
|
||||
### Лицензия
|
||||
Ну это даже не смешно, это вайбкод проект, делайте что хотите, а если по нормальному, то GPLv3, но не обижусь если что-то и проприектарное будет.
|
||||
|
||||
10
config.ini
10
config.ini
@@ -18,3 +18,13 @@ text = txt,md,html,htm,css,js,json,xml,csv
|
||||
audio = mp3,wav,ogg,flac,m4a,aac
|
||||
video = mp4,webm,ogv,mov,avi,mkv
|
||||
pdf = pdf
|
||||
|
||||
[security]
|
||||
cookie_name = folder_auth
|
||||
cookie_lifetime = 3600
|
||||
max_attempts = 5
|
||||
block_time = 900
|
||||
|
||||
[logging]
|
||||
enable = 1
|
||||
log_file = /home/romkazvo/www/cgi-bin/logs/access.log
|
||||
|
||||
BIN
imgs/1.png
Executable file
BIN
imgs/1.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
BIN
imgs/2.png
Executable file
BIN
imgs/2.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
BIN
imgs/3.png
Executable file
BIN
imgs/3.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
imgs/4.png
Executable file
BIN
imgs/4.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
213
src/auth.c
213
src/auth.c
@@ -1,13 +1,29 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include "auth.h"
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TOKEN_DIR "/tmp/trashbox_tokens"
|
||||
#define TOKEN_SALT "TrashBoxSecretSalt2024"
|
||||
|
||||
// ============================================
|
||||
// ПРОВЕРКА ПАРОЛЯ
|
||||
// ============================================
|
||||
|
||||
int check_folder_password(const char *folder_name, const char *password) {
|
||||
if (!password || !folder_name || password[0] == '\0') return 0;
|
||||
|
||||
FILE *f = fopen(g_config.passwd_file, "r");
|
||||
if (!f) return 1;
|
||||
if (!f) {
|
||||
fprintf(stderr, "DEBUG: cannot open passwd file: %s\n", g_config.passwd_file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char line[512];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
@@ -19,13 +35,14 @@ int check_folder_password(const char *folder_name, const char *password) {
|
||||
char *pass = colon + 1;
|
||||
const char *last_slash = strrchr(folder_name, '/');
|
||||
const char *base_name = last_slash ? last_slash + 1 : folder_name;
|
||||
|
||||
if (strcmp(folder, folder_name) == 0 || strcmp(folder, base_name) == 0) {
|
||||
fclose(f);
|
||||
return strcmp(pass, password) == 0;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int is_folder_protected(const char *folder_path) {
|
||||
@@ -50,3 +67,195 @@ int is_folder_protected(const char *folder_path) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ЛИМИТ ПОПЫТОК
|
||||
// ============================================
|
||||
|
||||
void get_attempts_file(char *buf, size_t size, const char *folder_name, const char *ip) {
|
||||
char safe_folder[256];
|
||||
strncpy(safe_folder, folder_name, sizeof(safe_folder) - 1);
|
||||
safe_folder[sizeof(safe_folder) - 1] = '\0';
|
||||
for (char *p = safe_folder; *p; p++) {
|
||||
if (*p == '/' || *p == '\\' || *p == '.') *p = '_';
|
||||
}
|
||||
snprintf(buf, size, "/tmp/trashbox_attempts_%s_%s", safe_folder, ip);
|
||||
}
|
||||
|
||||
int check_attempts(const char *folder_name, const char *ip) {
|
||||
if (!folder_name || !ip) return 0;
|
||||
|
||||
char attempts_file[256];
|
||||
get_attempts_file(attempts_file, sizeof(attempts_file), folder_name, ip);
|
||||
|
||||
FILE *f = fopen(attempts_file, "r");
|
||||
if (!f) return 0;
|
||||
|
||||
int attempts;
|
||||
time_t first_attempt_time;
|
||||
|
||||
if (fscanf(f, "%d %ld", &attempts, &first_attempt_time) != 2) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (now - first_attempt_time > g_config.block_time) {
|
||||
unlink(attempts_file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return attempts >= g_config.max_attempts;
|
||||
}
|
||||
|
||||
void add_attempt(const char *folder_name, const char *ip) {
|
||||
if (!folder_name || !ip) return;
|
||||
|
||||
char attempts_file[256];
|
||||
get_attempts_file(attempts_file, sizeof(attempts_file), folder_name, ip);
|
||||
|
||||
int attempts = 0;
|
||||
time_t first_attempt_time = time(NULL);
|
||||
|
||||
FILE *f = fopen(attempts_file, "r");
|
||||
if (f) {
|
||||
fscanf(f, "%d %ld", &attempts, &first_attempt_time);
|
||||
fclose(f);
|
||||
attempts++;
|
||||
} else {
|
||||
attempts = 1;
|
||||
}
|
||||
|
||||
f = fopen(attempts_file, "w");
|
||||
if (f) {
|
||||
fprintf(f, "%d %ld\n", attempts, first_attempt_time);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_attempts(const char *folder_name, const char *ip) {
|
||||
if (!folder_name || !ip) return;
|
||||
|
||||
char attempts_file[256];
|
||||
get_attempts_file(attempts_file, sizeof(attempts_file), folder_name, ip);
|
||||
unlink(attempts_file);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ТОКЕНЫ
|
||||
// ============================================
|
||||
|
||||
char* generate_token(const char *path) {
|
||||
// ============================================
|
||||
// ФИНАЛЬНЫЙ ФИКС: очищаем путь от любых нежелательных символов
|
||||
// ============================================
|
||||
char clean_path[1024];
|
||||
strncpy(clean_path, path, sizeof(clean_path) - 1);
|
||||
clean_path[sizeof(clean_path) - 1] = '\0';
|
||||
|
||||
// Удаляем всё после '%' и другие нежелательные символы
|
||||
char *p = clean_path;
|
||||
while (*p) {
|
||||
if (*p == '%' || *p == '\n' || *p == '\r') {
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
// Если путь пустой — используем "default"
|
||||
if (clean_path[0] == '\0') {
|
||||
strcpy(clean_path, "default");
|
||||
}
|
||||
|
||||
mkdir(TOKEN_DIR, 0700);
|
||||
|
||||
time_t now = time(NULL);
|
||||
char input[1024];
|
||||
snprintf(input, sizeof(input), "%ld_%s_%s", now, clean_path, TOKEN_SALT);
|
||||
|
||||
unsigned long hash = 0;
|
||||
for (int i = 0; input[i]; i++) {
|
||||
hash = hash * 31 + input[i];
|
||||
}
|
||||
|
||||
char token[256];
|
||||
snprintf(token, sizeof(token), "%lx_%ld", hash, now);
|
||||
|
||||
char token_file[512];
|
||||
snprintf(token_file, sizeof(token_file), "%s/%s", TOKEN_DIR, token);
|
||||
|
||||
FILE *f = fopen(token_file, "w");
|
||||
if (!f) return NULL;
|
||||
fprintf(f, "%s", clean_path);
|
||||
fclose(f);
|
||||
|
||||
return strdup(token);
|
||||
}
|
||||
|
||||
int check_token(const char *token, char *path, size_t path_size) {
|
||||
if (!token || !token[0]) return 0;
|
||||
|
||||
char token_file[512];
|
||||
snprintf(token_file, sizeof(token_file), "%s/%s", TOKEN_DIR, token);
|
||||
|
||||
FILE *f = fopen(token_file, "r");
|
||||
if (!f) return 0;
|
||||
|
||||
char stored_path[1024];
|
||||
if (fgets(stored_path, sizeof(stored_path), f) == NULL) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
stored_path[strcspn(stored_path, "\n")] = '\0';
|
||||
|
||||
// Обрезаем % в конце
|
||||
char *pp = stored_path;
|
||||
while (*pp) {
|
||||
if (*pp == '%' || *pp == '\n' || *pp == '\r') {
|
||||
*pp = '\0';
|
||||
break;
|
||||
}
|
||||
pp++;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(token_file, &st) != 0) return 0;
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (now - st.st_mtime > 3600) {
|
||||
unlink(token_file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
strncpy(path, stored_path, path_size - 1);
|
||||
path[path_size - 1] = '\0';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cleanup_old_tokens(void) {
|
||||
DIR *dir = opendir(TOKEN_DIR);
|
||||
if (!dir) return;
|
||||
|
||||
struct dirent *entry;
|
||||
time_t now = time(NULL);
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.') continue;
|
||||
|
||||
char token_file[512];
|
||||
snprintf(token_file, sizeof(token_file), "%s/%s", TOKEN_DIR, entry->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(token_file, &st) == 0) {
|
||||
if (now - st.st_mtime > 3600) {
|
||||
unlink(token_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
@@ -3,5 +3,12 @@
|
||||
|
||||
int is_folder_protected(const char *folder_path);
|
||||
int check_folder_password(const char *folder_name, const char *password);
|
||||
int check_attempts(const char *folder_name, const char *ip);
|
||||
void add_attempt(const char *folder_name, const char *ip);
|
||||
void clear_attempts(const char *folder_name, const char *ip);
|
||||
|
||||
char* generate_token(const char *path);
|
||||
int check_token(const char *token, char *path, size_t path_size);
|
||||
void cleanup_old_tokens(void);
|
||||
|
||||
#endif
|
||||
|
||||
53
src/check_token.c
Normal file
53
src/check_token.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TOKEN_DIR "/tmp/trashbox_tokens"
|
||||
|
||||
int main() {
|
||||
char *query_string = getenv("QUERY_STRING");
|
||||
if (!query_string) {
|
||||
printf("Content-type: text/plain\n\ninvalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *token_start = strstr(query_string, "token=");
|
||||
if (!token_start) {
|
||||
printf("Content-type: text/plain\n\ninvalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
token_start += 6;
|
||||
char *token_end = strchr(token_start, '&');
|
||||
int token_len = token_end ? (int)(token_end - token_start) : (int)strlen(token_start);
|
||||
if (token_len <= 0 || token_len >= 256) {
|
||||
printf("Content-type: text/plain\n\ninvalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char token[256];
|
||||
strncpy(token, token_start, token_len);
|
||||
token[token_len] = '\0';
|
||||
|
||||
char token_file[512];
|
||||
snprintf(token_file, sizeof(token_file), "%s/%s", TOKEN_DIR, token);
|
||||
|
||||
struct stat st;
|
||||
if (stat(token_file, &st) != 0) {
|
||||
printf("Content-type: text/plain\n\ninvalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (now - st.st_mtime > 3600) {
|
||||
unlink(token_file);
|
||||
printf("Content-type: text/plain\n\ninvalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Content-type: text/plain\n\nvalid");
|
||||
return 0;
|
||||
}
|
||||
18
src/config.c
18
src/config.c
@@ -86,6 +86,18 @@ int load_config(const char *path) {
|
||||
else if (strcmp(key, "video") == 0) strncpy(g_config.preview_video, value, sizeof(g_config.preview_video) - 1);
|
||||
else if (strcmp(key, "pdf") == 0) strncpy(g_config.preview_pdf, value, sizeof(g_config.preview_pdf) - 1);
|
||||
}
|
||||
else if (strcmp(current_section, "security") == 0) {
|
||||
if (strcmp(key, "max_attempts") == 0) g_config.max_attempts = atoi(value);
|
||||
else if (strcmp(key, "block_time") == 0) g_config.block_time = atoi(value);
|
||||
}
|
||||
else if (strcmp(current_section, "logging") == 0) {
|
||||
if (strcmp(key, "enable") == 0) {
|
||||
g_config.enable_access_log = atoi(value);
|
||||
} else if (strcmp(key, "log_file") == 0) {
|
||||
strncpy(g_config.log_file, value, sizeof(g_config.log_file) - 1);
|
||||
g_config.log_file[sizeof(g_config.log_file) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
@@ -104,6 +116,12 @@ void set_default_config(void) {
|
||||
strcpy(g_config.preview_audio, "mp3,wav,ogg,flac,m4a,aac");
|
||||
strcpy(g_config.preview_video, "mp4,webm,ogv,mov,avi,mkv");
|
||||
strcpy(g_config.preview_pdf, "pdf");
|
||||
|
||||
g_config.max_attempts = 5;
|
||||
g_config.block_time = 900;
|
||||
|
||||
strcpy(g_config.log_file, "/home/romkazvo/www/cgi-bin/logs/access.log");
|
||||
g_config.enable_access_log = 1;
|
||||
}
|
||||
|
||||
int is_string_in_list(const char *str, const char *list) {
|
||||
|
||||
@@ -15,6 +15,12 @@ typedef struct {
|
||||
char preview_audio[512];
|
||||
char preview_video[512];
|
||||
char preview_pdf[512];
|
||||
|
||||
int max_attempts;
|
||||
int block_time;
|
||||
|
||||
char log_file[1024];
|
||||
int enable_access_log;
|
||||
} config_t;
|
||||
|
||||
extern config_t g_config;
|
||||
|
||||
10
src/fs.c
10
src/fs.c
@@ -124,8 +124,7 @@ void search_recursive(const char *base_path, const char *display_path, const cha
|
||||
if (stat(full_path, &file_stat) == 0) {
|
||||
if (my_strcasestr(entry->d_name, search_term) != NULL) {
|
||||
entry_t *result = &results[*count];
|
||||
strncpy(result->name, entry->d_name, sizeof(result->name) - 1);
|
||||
result->name[sizeof(result->name) - 1] = '\0';
|
||||
snprintf(result->name, sizeof(result->name), "%s", entry->d_name);
|
||||
if (S_ISDIR(file_stat.st_mode)) {
|
||||
result->is_dir = 1;
|
||||
result->size = 0;
|
||||
@@ -135,7 +134,7 @@ void search_recursive(const char *base_path, const char *display_path, const cha
|
||||
strcpy(result->icon, "📁");
|
||||
result->is_protected = 0;
|
||||
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
||||
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
|
||||
snprintf(result->file_url, sizeof(result->file_url), "%s", display_subpath);
|
||||
(*count)++;
|
||||
} else if (S_ISREG(file_stat.st_mode)) {
|
||||
result->is_dir = 0;
|
||||
@@ -146,7 +145,7 @@ void search_recursive(const char *base_path, const char *display_path, const cha
|
||||
strcpy(result->icon, get_file_icon(entry->d_name));
|
||||
result->is_protected = 0;
|
||||
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
||||
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
|
||||
snprintf(result->file_url, sizeof(result->file_url), "%s", display_subpath);
|
||||
(*count)++;
|
||||
}
|
||||
}
|
||||
@@ -161,8 +160,10 @@ void search_recursive(const char *base_path, const char *display_path, const cha
|
||||
int compare_entries_sorted(const void *a, const void *b) {
|
||||
const entry_t *entryA = (const entry_t *)a;
|
||||
const entry_t *entryB = (const entry_t *)b;
|
||||
|
||||
if (entryA->is_dir && !entryB->is_dir) return -1;
|
||||
if (!entryA->is_dir && entryB->is_dir) return 1;
|
||||
|
||||
int result = 0;
|
||||
if (strcmp(g_params.sort_by, "size") == 0) {
|
||||
long long sizeA = entryA->is_dir ? entryA->dir_size : entryA->size;
|
||||
@@ -173,5 +174,6 @@ int compare_entries_sorted(const void *a, const void *b) {
|
||||
} else {
|
||||
result = strcasecmp(entryA->name, entryB->name);
|
||||
}
|
||||
|
||||
return result * g_params.sort_order;
|
||||
}
|
||||
|
||||
3
src/fs.h
3
src/fs.h
@@ -9,7 +9,7 @@ typedef struct {
|
||||
long size;
|
||||
char icon[8];
|
||||
char encoded_path[1024];
|
||||
char file_url[1024];
|
||||
char file_url[2048];
|
||||
int is_protected;
|
||||
time_t mtime;
|
||||
int file_count;
|
||||
@@ -21,6 +21,7 @@ typedef struct {
|
||||
int sort_order;
|
||||
char search[256];
|
||||
int recursive;
|
||||
char view[16];
|
||||
} query_params_t;
|
||||
|
||||
extern query_params_t g_params;
|
||||
|
||||
207
src/main.c
207
src/main.c
@@ -21,13 +21,146 @@ int main() {
|
||||
g_params.sort_order = 1;
|
||||
g_params.search[0] = '\0';
|
||||
g_params.recursive = 0;
|
||||
strcpy(g_params.view, "list");
|
||||
|
||||
char *method = getenv("REQUEST_METHOD");
|
||||
char *query_string = getenv("QUERY_STRING");
|
||||
char *remote_addr = getenv("REMOTE_ADDR");
|
||||
if (!remote_addr) remote_addr = "unknown";
|
||||
|
||||
char display_path[1024] = "";
|
||||
char safe_display_path[1024] = "";
|
||||
char base_path[1024];
|
||||
strcpy(base_path, g_config.base_path);
|
||||
|
||||
// ============================================
|
||||
// ОБРАБОТКА POST (пароль через форму)
|
||||
// ============================================
|
||||
if (method && strcmp(method, "POST") == 0) {
|
||||
char *content_length_str = getenv("CONTENT_LENGTH");
|
||||
if (content_length_str) {
|
||||
int content_length = atoi(content_length_str);
|
||||
if (content_length > 0 && content_length < 65536) {
|
||||
char *post_data = malloc(content_length + 1);
|
||||
if (post_data) {
|
||||
int read_len = fread(post_data, 1, content_length, stdin);
|
||||
post_data[read_len] = '\0';
|
||||
|
||||
char post_path[1024] = "";
|
||||
char post_password[256] = "";
|
||||
|
||||
char *p = post_data;
|
||||
while (*p) {
|
||||
if (strncmp(p, "path=", 5) == 0) {
|
||||
p += 5;
|
||||
char *end = strchr(p, '&');
|
||||
int len = end ? (int)(end - p) : (int)strlen(p);
|
||||
if (len > 0 && len < (int)sizeof(post_path) - 1) {
|
||||
char encoded[1024];
|
||||
strncpy(encoded, p, len);
|
||||
encoded[len] = '\0';
|
||||
url_decode_enhanced(encoded, post_path, sizeof(post_path));
|
||||
}
|
||||
p += len;
|
||||
if (*p == '&') p++;
|
||||
} else if (strncmp(p, "password=", 9) == 0) {
|
||||
p += 9;
|
||||
char *end = strchr(p, '&');
|
||||
int len = end ? (int)(end - p) : (int)strlen(p);
|
||||
if (len > 0 && len < (int)sizeof(post_password) - 1) {
|
||||
char encoded[256];
|
||||
strncpy(encoded, p, len);
|
||||
encoded[len] = '\0';
|
||||
url_decode_enhanced(encoded, post_password, sizeof(post_password));
|
||||
}
|
||||
p += len;
|
||||
if (*p == '&') p++;
|
||||
} else {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
free(post_data);
|
||||
|
||||
fprintf(stderr, "DEBUG POST: path='%s', password='%s'\n", post_path, post_password);
|
||||
|
||||
if (post_path[0] && post_password[0]) {
|
||||
if (check_attempts(post_path, remote_addr)) {
|
||||
char encoded_path[1024];
|
||||
url_encode(post_path, encoded_path, sizeof(encoded_path));
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=blocked\r\n\r\n", encoded_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *last_slash = strrchr(post_path, '/');
|
||||
const char *folder_name = last_slash ? last_slash + 1 : post_path;
|
||||
|
||||
fprintf(stderr, "DEBUG: checking folder='%s'\n", folder_name);
|
||||
|
||||
if (check_folder_password(folder_name, post_password)) {
|
||||
fprintf(stderr, "DEBUG: password OK!\n");
|
||||
clear_attempts(post_path, remote_addr);
|
||||
|
||||
char encoded_path[1024];
|
||||
url_encode(post_path, encoded_path, sizeof(encoded_path));
|
||||
|
||||
char clean_path[1024];
|
||||
strncpy(clean_path, post_path, sizeof(clean_path) - 1);
|
||||
clean_path[sizeof(clean_path) - 1] = '\0';
|
||||
|
||||
char *pp = clean_path;
|
||||
while (*pp) {
|
||||
if (*pp == '%') {
|
||||
*pp = '\0';
|
||||
break;
|
||||
}
|
||||
pp++;
|
||||
}
|
||||
|
||||
fprintf(stderr, "DEBUG: clean_path before token = '%s'\n", clean_path);
|
||||
|
||||
char *token = generate_token(clean_path);
|
||||
if (token) {
|
||||
fprintf(stderr, "DEBUG: token generated: %s\n", token);
|
||||
// ============================================
|
||||
// РЕДИРЕКТ С ТОКЕНОМ
|
||||
// ============================================
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&token=%s\r\n", encoded_path, token);
|
||||
printf("\r\n");
|
||||
free(token);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "DEBUG: token generation failed\n");
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s\r\n", encoded_path);
|
||||
printf("\r\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "DEBUG: password FAILED!\n");
|
||||
add_attempt(post_path, remote_addr);
|
||||
|
||||
char encoded_path[1024];
|
||||
url_encode(post_path, encoded_path, sizeof(encoded_path));
|
||||
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n", encoded_path);
|
||||
printf("\r\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "DEBUG: path or password empty!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ОБРАБОТКА GET
|
||||
// ============================================
|
||||
if (query_string) {
|
||||
char *path_start = strstr(query_string, "path=");
|
||||
if (path_start) {
|
||||
@@ -61,6 +194,27 @@ int main() {
|
||||
}
|
||||
}
|
||||
|
||||
char *order_start = strstr(query_string, "order=");
|
||||
if (order_start) {
|
||||
order_start += 6;
|
||||
if (*order_start == '1') {
|
||||
g_params.sort_order = 1;
|
||||
} else if (*order_start == '-') {
|
||||
g_params.sort_order = -1;
|
||||
}
|
||||
}
|
||||
|
||||
char *view_start = strstr(query_string, "view=");
|
||||
if (view_start) {
|
||||
view_start += 5;
|
||||
char *view_end = strchr(view_start, '&');
|
||||
int view_len = view_end ? (int)(view_end - view_start) : (int)strlen(view_start);
|
||||
if (view_len > 0 && view_len < (int)sizeof(g_params.view) - 1) {
|
||||
strncpy(g_params.view, view_start, view_len);
|
||||
g_params.view[view_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
char *search_start = strstr(query_string, "search=");
|
||||
if (search_start) {
|
||||
search_start += 7;
|
||||
@@ -76,54 +230,13 @@ int main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка пароля (без кук)
|
||||
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 ? (int)(pass_end - pass_start) : (int)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 (decoded_pass[0] == '\0') {
|
||||
char encoded_path[1024];
|
||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||
return 0;
|
||||
}
|
||||
if (!check_folder_password(folder_name, decoded_pass)) {
|
||||
char encoded_path[1024];
|
||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
char encoded_path[1024];
|
||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||
printf("Status: 302 Found\r\n");
|
||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static time_t last_cleanup = 0;
|
||||
time_t now = time(NULL);
|
||||
if (now - last_cleanup > 3600) {
|
||||
cleanup_old_tokens();
|
||||
last_cleanup = now;
|
||||
}
|
||||
|
||||
printf("Content-type: text/html; charset=utf-8\n\n");
|
||||
|
||||
280
src/render.c
280
src/render.c
@@ -32,9 +32,35 @@ void buffer_append(buffer_t *buf, const char *str) {
|
||||
|
||||
void buffer_free(buffer_t *buf) { free(buf->data); }
|
||||
|
||||
void get_token_part(char *buf, size_t size) {
|
||||
buf[0] = '\0';
|
||||
char *query_string = getenv("QUERY_STRING");
|
||||
if (!query_string) return;
|
||||
|
||||
char *token_start = strstr(query_string, "token=");
|
||||
if (!token_start) return;
|
||||
|
||||
token_start += 6;
|
||||
char *token_end = strchr(token_start, '&');
|
||||
int token_len = token_end ? (int)(token_end - token_start) : (int)strlen(token_start);
|
||||
if (token_len > 0 && token_len < (int)size - 1) {
|
||||
strncpy(buf, token_start, token_len);
|
||||
buf[token_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
||||
char token_part[256] = "";
|
||||
get_token_part(token_part, sizeof(token_part));
|
||||
|
||||
buffer_append(buf, "<div class=\"breadcrumb\">\n");
|
||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\">🏠 Главная</a>");
|
||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi");
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, "?");
|
||||
buffer_append(buf, token_part);
|
||||
}
|
||||
buffer_append(buf, "\">🏠 Главная</a>");
|
||||
|
||||
if (display_path && display_path[0]) {
|
||||
char temp_path[1024] = "";
|
||||
char encoded[2048];
|
||||
@@ -47,9 +73,17 @@ void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
||||
url_encode(temp_path, encoded, sizeof(encoded));
|
||||
char safe_token[512];
|
||||
html_escape(token, safe_token, sizeof(safe_token));
|
||||
char link[4096];
|
||||
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, safe_token);
|
||||
buffer_append(buf, link);
|
||||
|
||||
buffer_append(buf, "<a href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, encoded);
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, "&");
|
||||
buffer_append(buf, token_part);
|
||||
}
|
||||
buffer_append(buf, "\">");
|
||||
buffer_append(buf, safe_token);
|
||||
buffer_append(buf, "</a>");
|
||||
|
||||
token = strtok(NULL, "/");
|
||||
}
|
||||
free(path_copy);
|
||||
@@ -58,6 +92,24 @@ void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
||||
}
|
||||
|
||||
void print_password_form(buffer_t *buf, const char *folder_name) {
|
||||
char *remote_addr = getenv("REMOTE_ADDR");
|
||||
if (!remote_addr) remote_addr = "unknown";
|
||||
|
||||
if (check_attempts(folder_name, remote_addr)) {
|
||||
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>Слишком много неудачных попыток. Подождите 15 минут.</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");
|
||||
return;
|
||||
}
|
||||
|
||||
char encoded_path[1024];
|
||||
url_encode(folder_name, encoded_path, sizeof(encoded_path));
|
||||
|
||||
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");
|
||||
@@ -67,16 +119,15 @@ void print_password_form(buffer_t *buf, const char *folder_name) {
|
||||
html_escape(folder_name, safe_name, sizeof(safe_name));
|
||||
buffer_append(buf, safe_name);
|
||||
buffer_append(buf, "</strong></p>\n");
|
||||
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
|
||||
buffer_append(buf, " <form method=\"POST\" 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, " <input type=\"password\" name=\"password\" placeholder=\"Введите пароль\" required autofocus>\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, "❌ Неверный пароль! Попробуйте снова.");
|
||||
@@ -88,62 +139,85 @@ void print_password_form(buffer_t *buf, const char *folder_name) {
|
||||
}
|
||||
|
||||
void print_content(buffer_t *buf, const char *base_path, const char *display_path) {
|
||||
fprintf(stderr, "DEBUG: print_content START, display_path='%s'\n", display_path ? display_path : "(null)");
|
||||
|
||||
// ============================================
|
||||
// ОБЪЯВЛЯЕМ entries В НАЧАЛЕ ФУНКЦИИ
|
||||
// ============================================
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
struct stat file_stat;
|
||||
char full_path[1024];
|
||||
entry_t *entries = malloc(sizeof(entry_t) * g_config.max_entries);
|
||||
if (!entries) {
|
||||
buffer_append(buf, "<div class=\"error\">Ошибка выделения памяти</div>");
|
||||
return;
|
||||
}
|
||||
entry_t *entries = NULL;
|
||||
int entry_count = 0, dir_count = 0, file_count = 0;
|
||||
long long total_size = 0;
|
||||
int is_recursive_search = 0;
|
||||
char *remote_addr = getenv("REMOTE_ADDR");
|
||||
if (!remote_addr) remote_addr = "unknown";
|
||||
|
||||
// === ПАРСИМ ПАРОЛЬ ДО ВСЕГО ОСТАЛЬНОГО ===
|
||||
char folder_name[256] = "";
|
||||
char *password = NULL;
|
||||
char *query_string = getenv("QUERY_STRING");
|
||||
int is_root = 0;
|
||||
if (!query_string || !strstr(query_string, "path=")) {
|
||||
fprintf(stderr, "DEBUG: no path=, rendering root\n");
|
||||
is_root = 1;
|
||||
}
|
||||
|
||||
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 ? (int)(pass_end - pass_start) : (int)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));
|
||||
password = decoded_pass;
|
||||
char token_part[256] = "";
|
||||
get_token_part(token_part, sizeof(token_part));
|
||||
fprintf(stderr, "DEBUG: token_part='%s'\n", token_part);
|
||||
|
||||
int has_valid_token = 0;
|
||||
|
||||
if (!is_root && query_string) {
|
||||
char *token_start = strstr(query_string, "token=");
|
||||
if (token_start) {
|
||||
token_start += 6;
|
||||
char *token_end = strchr(token_start, '&');
|
||||
int token_len = token_end ? (int)(token_end - token_start) : (int)strlen(token_start);
|
||||
if (token_len > 0 && token_len < 256) {
|
||||
char token[256];
|
||||
strncpy(token, token_start, token_len);
|
||||
token[token_len] = '\0';
|
||||
|
||||
char token_path[1024];
|
||||
if (check_token(token, token_path, sizeof(token_path))) {
|
||||
if (strcmp(token_path, display_path) == 0) {
|
||||
has_valid_token = 1;
|
||||
fprintf(stderr, "DEBUG: valid token for '%s'\n", display_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (display_path && display_path[0]) {
|
||||
if (!is_root && !has_valid_token && 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 *folder_name = last_slash ? last_slash + 1 : (char*)display_path;
|
||||
|
||||
// === ОТЛАДКА ===
|
||||
fprintf(stderr, "DEBUG: folder_name = '%s'\n", folder_name);
|
||||
fprintf(stderr, "DEBUG: password = '%s'\n", password ? password : "NULL");
|
||||
if (password) {
|
||||
fprintf(stderr, "DEBUG: check_folder_password = %d\n", check_folder_password(folder_name, password));
|
||||
if (query_string && strstr(query_string, "error=1")) {
|
||||
fprintf(stderr, "DEBUG: error=1, showing password form\n");
|
||||
print_password_form(buf, folder_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_folder_protected(folder_name)) {
|
||||
if (!password || !check_folder_password(folder_name, password)) {
|
||||
fprintf(stderr, "DEBUG: no valid token, showing password form\n");
|
||||
// Добавляем скрипт очистки localStorage
|
||||
buffer_append(buf, "<script>localStorage.removeItem('trashbox_token'); localStorage.removeItem('trashbox_token_data');</script>\n");
|
||||
print_password_form(buf, folder_name);
|
||||
free(entries);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ВЫДЕЛЯЕМ ПАМЯТЬ ДЛЯ entries
|
||||
// ============================================
|
||||
entries = malloc(sizeof(entry_t) * g_config.max_entries);
|
||||
if (!entries) {
|
||||
buffer_append(buf, "<div class=\"error\">Ошибка выделения памяти</div>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_params.search[0] != '\0') {
|
||||
int result_count = 0;
|
||||
search_recursive(g_config.base_path, "", g_params.search, entries, &result_count, g_config.max_entries);
|
||||
@@ -187,7 +261,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
buffer_append(buf, " <div class=\"icon\">📁</div>\n");
|
||||
char *display_base = strstr(base_path, "/www/");
|
||||
if (!display_base) display_base = (char*)base_path;
|
||||
buffer_append(buf, " <h3>Ошибка открытия: ");
|
||||
buffer_append(buf, " <h3>Ошибка открытия директории: ");
|
||||
buffer_append(buf, display_base);
|
||||
buffer_append(buf, "</h3>\n");
|
||||
buffer_append(buf, " <p>Папка не найдена</p>\n");
|
||||
@@ -210,8 +284,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
if (hidden) continue;
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
||||
if (stat(full_path, &file_stat) == 0) {
|
||||
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
|
||||
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
|
||||
snprintf(entries[entry_count].name, sizeof(entries[0].name), "%s", entry->d_name);
|
||||
if (S_ISDIR(file_stat.st_mode)) {
|
||||
entries[entry_count].is_dir = 1;
|
||||
entries[entry_count].size = 0;
|
||||
@@ -240,7 +313,9 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
entries[entry_count].is_protected = 0;
|
||||
char encoded_filename[1024];
|
||||
url_encode(entry->d_name, encoded_filename, sizeof(encoded_filename));
|
||||
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", encoded_filename);
|
||||
char temp_url[2048];
|
||||
snprintf(temp_url, sizeof(temp_url), "%s%s%s", display_path, display_path[0] ? "/" : "", encoded_filename);
|
||||
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s", temp_url);
|
||||
total_size += file_stat.st_size;
|
||||
file_count++;
|
||||
} else {
|
||||
@@ -260,42 +335,92 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qsort(entries, entry_count, sizeof(entry_t), compare_entries_sorted);
|
||||
|
||||
char encoded_path[1024];
|
||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||
char safe_search[512];
|
||||
html_escape(g_params.search, safe_search, sizeof(safe_search));
|
||||
|
||||
int next_order = g_params.sort_order * -1;
|
||||
char order_str[4];
|
||||
snprintf(order_str, sizeof(order_str), "%d", next_order);
|
||||
|
||||
char active_name[64] = "";
|
||||
char active_size[64] = "";
|
||||
char active_date[64] = "";
|
||||
if (strcmp(g_params.sort_by, "name") == 0) { strcpy(active_name, " active"); }
|
||||
else if (strcmp(g_params.sort_by, "size") == 0) { strcpy(active_size, " active"); }
|
||||
else if (strcmp(g_params.sort_by, "date") == 0) { strcpy(active_date, " active"); }
|
||||
|
||||
char arrow[8] = "";
|
||||
if (g_params.sort_order == 1) strcpy(arrow, "🔽");
|
||||
else strcpy(arrow, "🔼");
|
||||
|
||||
char link_with_token[512];
|
||||
if (token_part[0]) {
|
||||
snprintf(link_with_token, sizeof(link_with_token), "&%s", token_part);
|
||||
} else {
|
||||
link_with_token[0] = '\0';
|
||||
}
|
||||
|
||||
buffer_append(buf, "<div class=\"toolbar\">\n");
|
||||
buffer_append(buf, " <div class=\"search-box\">\n");
|
||||
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
|
||||
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
|
||||
buffer_append(buf, encoded_path);
|
||||
buffer_append(buf, "\">\n");
|
||||
buffer_append(buf, " <input type=\"text\" name=\"search\" placeholder=\"🔍 Поиск по всему сайту...\" value=\"");
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, " <input type=\"hidden\" name=\"");
|
||||
buffer_append(buf, token_part);
|
||||
buffer_append(buf, "\">\n");
|
||||
}
|
||||
buffer_append(buf, " <input type=\"text\" name=\"search\" placeholder=\"🔍 Поиск по файлам\" value=\"");
|
||||
buffer_append(buf, safe_search);
|
||||
buffer_append(buf, "\">\n");
|
||||
buffer_append(buf, " <button type=\"submit\">Найти</button>\n");
|
||||
buffer_append(buf, " </form>\n");
|
||||
buffer_append(buf, " </div>\n");
|
||||
buffer_append(buf, " <div class=\"sort-buttons\">\n");
|
||||
buffer_append(buf, " <span class=\"sort-label\">Сортировка:</span>\n");
|
||||
buffer_append(buf, " <div class=\"settings-group\">\n");
|
||||
buffer_append(buf, " <span class=\"settings-label\">Сорт.</span>\n");
|
||||
|
||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, encoded_path);
|
||||
buffer_append(buf, "&sort=name\" class=\"sort-btn");
|
||||
if (strcmp(g_params.sort_by, "name") == 0) buffer_append(buf, " active");
|
||||
buffer_append(buf, "\">📝 Имя</a>\n");
|
||||
buffer_append(buf, "&sort=name&order=");
|
||||
buffer_append(buf, order_str);
|
||||
buffer_append(buf, link_with_token);
|
||||
buffer_append(buf, "\" class=\"sort-btn");
|
||||
buffer_append(buf, active_name);
|
||||
buffer_append(buf, "\">📝 Имя ");
|
||||
if (strcmp(g_params.sort_by, "name") == 0) buffer_append(buf, arrow);
|
||||
buffer_append(buf, "</a>\n");
|
||||
|
||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, encoded_path);
|
||||
buffer_append(buf, "&sort=size\" class=\"sort-btn");
|
||||
if (strcmp(g_params.sort_by, "size") == 0) buffer_append(buf, " active");
|
||||
buffer_append(buf, "\">📊 Размер</a>\n");
|
||||
buffer_append(buf, "&sort=size&order=");
|
||||
buffer_append(buf, order_str);
|
||||
buffer_append(buf, link_with_token);
|
||||
buffer_append(buf, "\" class=\"sort-btn");
|
||||
buffer_append(buf, active_size);
|
||||
buffer_append(buf, "\">📊 Размер ");
|
||||
if (strcmp(g_params.sort_by, "size") == 0) buffer_append(buf, arrow);
|
||||
buffer_append(buf, "</a>\n");
|
||||
|
||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, encoded_path);
|
||||
buffer_append(buf, "&sort=date\" class=\"sort-btn");
|
||||
if (strcmp(g_params.sort_by, "date") == 0) buffer_append(buf, " active");
|
||||
buffer_append(buf, "\">📅 Дата</a>\n");
|
||||
buffer_append(buf, "&sort=date&order=");
|
||||
buffer_append(buf, order_str);
|
||||
buffer_append(buf, link_with_token);
|
||||
buffer_append(buf, "\" class=\"sort-btn");
|
||||
buffer_append(buf, active_date);
|
||||
buffer_append(buf, "\">📅 Дата ");
|
||||
if (strcmp(g_params.sort_by, "date") == 0) buffer_append(buf, arrow);
|
||||
buffer_append(buf, "</a>\n");
|
||||
|
||||
buffer_append(buf, " </div>\n");
|
||||
buffer_append(buf, "</div>\n");
|
||||
|
||||
if (is_recursive_search) {
|
||||
buffer_append(buf, "<div style=\"font-size:0.85rem;color:var(--text-muted);margin-bottom:0.5rem;padding:0.25rem 0.5rem;\">");
|
||||
buffer_append(buf, "🔍 Результаты поиска по всему сайту: ");
|
||||
@@ -304,6 +429,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
buffer_append(buf, tmp);
|
||||
buffer_append(buf, " файлов/папок найдено</div>\n");
|
||||
}
|
||||
|
||||
buffer_append(buf, "<ul>\n");
|
||||
int has_dirs = 0;
|
||||
for (int i = 0; i < entry_count; i++) if (entries[i].is_dir) { has_dirs = 1; break; }
|
||||
@@ -318,6 +444,13 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
buffer_append(buf, " <li>\n");
|
||||
if (entries[i].is_protected) {
|
||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, " <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, entries[i].encoded_path);
|
||||
buffer_append(buf, "&");
|
||||
buffer_append(buf, token_part);
|
||||
buffer_append(buf, "\">\n");
|
||||
} else {
|
||||
buffer_append(buf, " <a class=\"dir-link protected-folder\" href=\"javascript:void(0)\" onclick=\"showPasswordForm('");
|
||||
buffer_append(buf, entries[i].encoded_path);
|
||||
buffer_append(buf, "', '");
|
||||
@@ -325,6 +458,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
html_escape(entries[i].name, safe_name, sizeof(safe_name));
|
||||
buffer_append(buf, safe_name);
|
||||
buffer_append(buf, "')\">\n");
|
||||
}
|
||||
buffer_append(buf, " <span class=\"file-icon\">📁</span>\n");
|
||||
buffer_append(buf, " <span class=\"file-name\">");
|
||||
if (is_recursive_search) {
|
||||
@@ -348,17 +482,23 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
buffer_append(buf, "')\" title=\"Ввести пароль\">🔑</button>\n");
|
||||
buffer_append(buf, " <button class=\"copy-btn\" onclick=\"copyLink('/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, entries[i].encoded_path);
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, "&");
|
||||
buffer_append(buf, token_part);
|
||||
}
|
||||
buffer_append(buf, "')\" title=\"Копировать ссылку на защищённую папку\">🔗</button>\n");
|
||||
buffer_append(buf, " <span class=\"file-meta size\">—</span>\n");
|
||||
buffer_append(buf, " </div>\n");
|
||||
buffer_append(buf, " </div>\n");
|
||||
} else {
|
||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||
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, " <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, entries[i].encoded_path);
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, "&");
|
||||
buffer_append(buf, token_part);
|
||||
}
|
||||
buffer_append(buf, "\">\n");
|
||||
buffer_append(buf, " <span class=\"file-icon\">");
|
||||
buffer_append(buf, entries[i].icon);
|
||||
buffer_append(buf, "</span>\n");
|
||||
@@ -377,6 +517,10 @@ 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, " <button class=\"copy-btn\" onclick=\"copyLink('/cgi-bin/index.cgi?path=");
|
||||
buffer_append(buf, entries[i].encoded_path);
|
||||
if (token_part[0]) {
|
||||
buffer_append(buf, "&");
|
||||
buffer_append(buf, token_part);
|
||||
}
|
||||
buffer_append(buf, "')\" title=\"Копировать ссылку на папку\">🔗</button>\n");
|
||||
if (entries[i].dir_size > 0) {
|
||||
char size_str[32];
|
||||
@@ -473,14 +617,18 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
||||
buffer_append(buf, stats);
|
||||
}
|
||||
free(entries);
|
||||
fprintf(stderr, "DEBUG: print_content END\n");
|
||||
}
|
||||
|
||||
void print_template(const char *base_path, const char *display_path) {
|
||||
FILE *file = fopen(g_config.template_path, "r");
|
||||
if (!file) {
|
||||
fprintf(stderr, "DEBUG: cannot open template: %s\n", g_config.template_path);
|
||||
printf("Error: Cannot read template\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "DEBUG: template opened successfully\n");
|
||||
|
||||
buffer_t output;
|
||||
buffer_init(&output);
|
||||
char line[4096];
|
||||
@@ -494,6 +642,16 @@ void print_template(const char *base_path, const char *display_path) {
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
fprintf(stderr, "DEBUG: output size = %zu bytes\n", output.size);
|
||||
if (output.size == 0) {
|
||||
fprintf(stderr, "DEBUG: WARNING! output is empty!\n");
|
||||
buffer_free(&output);
|
||||
return;
|
||||
}
|
||||
fwrite(output.data, 1, output.size, stdout);
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "DEBUG: fwrite done, flushed\n");
|
||||
|
||||
buffer_free(&output);
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ void buffer_init(buffer_t *buf);
|
||||
void buffer_append(buffer_t *buf, const char *str);
|
||||
void buffer_free(buffer_t *buf);
|
||||
void print_template(const char *base_path, const char *display_path);
|
||||
void render_folder(buffer_t *buf, const char *base_path, const char *display_path);
|
||||
|
||||
#endif
|
||||
|
||||
55
src/status.c
55
src/status.c
@@ -1,3 +1,4 @@
|
||||
// /home/romkazvo/www/cgi-bin/src/status.c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -35,18 +36,62 @@ int get_process_memory(const char *process_name, long *memory_kb) {
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
float get_cpu_load() {
|
||||
FILE *fp = fopen("/proc/loadavg", "r");
|
||||
if (!fp) return 0.0f;
|
||||
|
||||
float load;
|
||||
fscanf(fp, "%f", &load);
|
||||
fclose(fp);
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
void get_memory_usage(long *used_mb, long *total_mb) {
|
||||
FILE *fp = fopen("/proc/meminfo", "r");
|
||||
if (!fp) {
|
||||
*used_mb = 0;
|
||||
*total_mb = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[128];
|
||||
long total = 0, available = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (sscanf(line, "MemTotal: %ld kB", &total) == 1) {
|
||||
// сохраняем
|
||||
} else if (sscanf(line, "MemAvailable: %ld kB", &available) == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
*total_mb = total / 1024;
|
||||
*used_mb = (total - available) / 1024;
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("Content-type: text/plain; charset=utf-8\n\n");
|
||||
|
||||
long busybox_kb = 0, nginx_kb = 0;
|
||||
|
||||
get_process_memory("busybox", &busybox_kb);
|
||||
nginx_kb = get_nginx_memory();
|
||||
|
||||
printf("BusyBox: %ld KB | Nginx: %ld MB | Всего: %.1f MB",
|
||||
busybox_kb,
|
||||
nginx_kb / 1024,
|
||||
(busybox_kb + nginx_kb) / 1024.0);
|
||||
double busybox_mb = busybox_kb / 1024.0;
|
||||
double nginx_mb = nginx_kb / 1024.0;
|
||||
double total_mb = busybox_mb + nginx_mb;
|
||||
|
||||
float cpu_load = get_cpu_load();
|
||||
|
||||
long used_mb = 0, total_sys_mb = 0;
|
||||
get_memory_usage(&used_mb, &total_sys_mb);
|
||||
|
||||
// Две строки
|
||||
printf("BusyBox: %.1f MB | Nginx: %.1f MB | Всего: %.1f MB\n",
|
||||
busybox_mb, nginx_mb, total_mb);
|
||||
printf("CPU Load: %.2f | Память: %ld / %ld MB (%.0f%%)",
|
||||
cpu_load, used_mb, total_sys_mb, total_sys_mb > 0 ? (float)used_mb / total_sys_mb * 100 : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ void url_decode_enhanced(const char *src, char *dst, size_t dst_size) {
|
||||
}
|
||||
src += 3;
|
||||
} else {
|
||||
// ============================================
|
||||
// ФИКС: если % не валидный — просто пропускаем его
|
||||
// ============================================
|
||||
src++;
|
||||
}
|
||||
} else if (*src == '+') {
|
||||
|
||||
143
template.html
143
template.html
@@ -130,14 +130,21 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const statusEl = document.getElementById('status');
|
||||
if (!statusEl) return;
|
||||
|
||||
setTimeout(function() {
|
||||
fetch('/cgi-bin/status.cgi')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('status').innerHTML = data;
|
||||
statusEl.innerHTML = data.replace(/\n/g, '<br>');
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById('status').innerHTML = 'Ошибка загрузки статуса';
|
||||
.catch(function() {
|
||||
statusEl.innerHTML = 'Ошибка загрузки статуса';
|
||||
});
|
||||
}, 300);
|
||||
})();
|
||||
</script>
|
||||
</footer>
|
||||
|
||||
@@ -154,7 +161,55 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ============================================
|
||||
// СОХРАНЯЕМ ТОКЕН В localStorage
|
||||
// ============================================
|
||||
(function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get('token');
|
||||
if (token) {
|
||||
localStorage.setItem('trashbox_token', token);
|
||||
if (window.history && window.history.replaceState) {
|
||||
const newUrl = window.location.pathname + window.location.search.replace(/[&?]token=[^&]*/, '').replace(/^&/, '?').replace(/\?$/, '');
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// ============================================
|
||||
// ДОБАВЛЯЕМ ТОКЕН ВО ВСЕ ССЫЛКИ
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const savedToken = localStorage.getItem('trashbox_token');
|
||||
if (savedToken) {
|
||||
document.querySelectorAll('a[href*="/cgi-bin/index.cgi"]').forEach(function(link) {
|
||||
if (!link.href.includes('token=')) {
|
||||
link.href += (link.href.includes('?') ? '&' : '?') + 'token=' + savedToken;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// ГЛОБАЛЬНЫЕ ФУНКЦИИ
|
||||
// ============================================
|
||||
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ПОКАЗ ФОРМЫ ПАРОЛЯ (с проверкой токена)
|
||||
// ============================================
|
||||
window.showPasswordForm = function(encodedPath, folderName) {
|
||||
const savedToken = localStorage.getItem('trashbox_token');
|
||||
if (savedToken) {
|
||||
window.location.href = '/cgi-bin/index.cgi?path=' + encodedPath + '&token=' + savedToken;
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById('previewModal');
|
||||
const content = document.getElementById('previewContent');
|
||||
const title = document.getElementById('previewTitle');
|
||||
@@ -166,13 +221,10 @@
|
||||
}
|
||||
|
||||
frame.style.display = 'none';
|
||||
|
||||
title.textContent = '🔒 Введите пароль для папки: ' + folderName;
|
||||
|
||||
const oldContainer = document.getElementById('passwordFormContainer');
|
||||
if (oldContainer) {
|
||||
oldContainer.remove();
|
||||
}
|
||||
if (oldContainer) oldContainer.remove();
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.id = 'passwordFormContainer';
|
||||
@@ -185,7 +237,7 @@
|
||||
<p style="color:var(--text-secondary);margin-bottom:1.5rem;font-size:0.95rem;">
|
||||
Введите пароль для доступа к <strong style="color:var(--text-primary);word-break:break-all;">${folderName}</strong>
|
||||
</p>
|
||||
<form method="GET" action="/cgi-bin/index.cgi" onsubmit="return validatePasswordForm(this)">
|
||||
<form method="POST" action="/cgi-bin/index.cgi" onsubmit="return validatePasswordForm(this)">
|
||||
<input type="hidden" name="path" value="${encodedPath}">
|
||||
<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>
|
||||
@@ -196,7 +248,6 @@
|
||||
`;
|
||||
|
||||
content.appendChild(container);
|
||||
|
||||
content.style.width = '500px';
|
||||
content.style.height = 'auto';
|
||||
content.style.maxWidth = '90vw';
|
||||
@@ -220,25 +271,34 @@
|
||||
const password = form.querySelector('input[type="password"]').value;
|
||||
const errorMsg = form.parentElement.querySelector('.error-msg');
|
||||
if (!password || password.length < 1) {
|
||||
if (errorMsg) {
|
||||
errorMsg.textContent = '❌ Пожалуйста, введите пароль';
|
||||
}
|
||||
if (errorMsg) errorMsg.textContent = '❌ Пожалуйста, введите пароль';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
window.downloadFile = function(path) {
|
||||
let cleanPath = path;
|
||||
cleanPath = cleanPath.replace(/[?&]token=[^&]*/g, '');
|
||||
cleanPath = cleanPath.replace(/[?&]$/, '');
|
||||
|
||||
let fileName = cleanPath.split('/').pop().split('?')[0];
|
||||
fileName = decodeURIComponent(fileName);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = path;
|
||||
link.download = path.split('/').pop();
|
||||
link.href = cleanPath;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
window.copyLink = function(path) {
|
||||
const url = window.location.origin + path;
|
||||
let cleanPath = path;
|
||||
cleanPath = cleanPath.replace(/[?&]token=[^&]*/g, '');
|
||||
cleanPath = cleanPath.replace(/[?&]$/, '');
|
||||
if (!cleanPath || cleanPath === '') cleanPath = '/';
|
||||
const url = window.location.origin + cleanPath;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(url).then(function() {
|
||||
showNotification('✅ Ссылка скопирована!');
|
||||
@@ -281,10 +341,7 @@
|
||||
}
|
||||
|
||||
window.scrollToTop = function() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
@@ -299,13 +356,10 @@
|
||||
window.toggleTheme = function() {
|
||||
const isDark = document.body.classList.contains('dark-theme');
|
||||
const newTheme = isDark ? 'light' : 'dark';
|
||||
|
||||
document.body.classList.toggle('dark-theme');
|
||||
document.documentElement.classList.toggle('dark-theme');
|
||||
|
||||
const button = document.querySelector('.theme-toggle');
|
||||
button.textContent = isDark ? '🌙' : '☀️';
|
||||
|
||||
const date = new Date();
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
document.cookie = 'theme=' + newTheme + '; expires=' + date.toUTCString() + '; path=/';
|
||||
@@ -318,12 +372,9 @@
|
||||
const title = document.getElementById('previewTitle');
|
||||
|
||||
const oldContainer = document.getElementById('passwordFormContainer');
|
||||
if (oldContainer) {
|
||||
oldContainer.remove();
|
||||
}
|
||||
if (oldContainer) oldContainer.remove();
|
||||
|
||||
frame.style.display = 'block';
|
||||
|
||||
const fileName = fileUrl.split('/').pop();
|
||||
const decodedFileName = decodeURIComponent(fileName);
|
||||
title.textContent = decodedFileName;
|
||||
@@ -350,25 +401,18 @@
|
||||
if (imageTypes.includes(fileExt)) {
|
||||
frame.style.display = 'none';
|
||||
imgContainer.style.display = 'flex';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = fileUrl;
|
||||
img.alt = fileName;
|
||||
img.className = 'preview-image';
|
||||
|
||||
img.onload = function() {
|
||||
adjustModalForImage(this);
|
||||
};
|
||||
|
||||
img.onload = function() { adjustModalForImage(this); };
|
||||
img.onerror = function() {
|
||||
console.error('Failed to load image:', fileUrl);
|
||||
frame.style.display = 'block';
|
||||
imgContainer.style.display = 'none';
|
||||
frame.src = fileUrl;
|
||||
};
|
||||
|
||||
imgContainer.appendChild(img);
|
||||
|
||||
content.style.width = '90vw';
|
||||
content.style.height = '90vh';
|
||||
content.style.maxWidth = 'none';
|
||||
@@ -378,11 +422,9 @@
|
||||
content.style.left = 'auto';
|
||||
content.style.top = 'auto';
|
||||
content.style.transform = 'none';
|
||||
|
||||
} else {
|
||||
frame.style.display = 'block';
|
||||
imgContainer.style.display = 'none';
|
||||
|
||||
if (textTypes.includes(fileExt)) {
|
||||
content.style.width = '80vw';
|
||||
content.style.height = '70vh';
|
||||
@@ -410,13 +452,11 @@
|
||||
content.style.maxWidth = '1000px';
|
||||
content.style.maxHeight = '800px';
|
||||
}
|
||||
|
||||
content.style.margin = '2% auto';
|
||||
content.style.position = 'relative';
|
||||
content.style.left = 'auto';
|
||||
content.style.top = 'auto';
|
||||
content.style.transform = 'none';
|
||||
|
||||
frame.src = fileUrl;
|
||||
}
|
||||
|
||||
@@ -428,22 +468,13 @@
|
||||
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);
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -464,9 +495,7 @@
|
||||
frame.style.display = 'block';
|
||||
|
||||
const oldContainer = document.getElementById('passwordFormContainer');
|
||||
if (oldContainer) {
|
||||
oldContainer.remove();
|
||||
}
|
||||
if (oldContainer) oldContainer.remove();
|
||||
|
||||
content.style.width = '';
|
||||
content.style.height = '';
|
||||
@@ -491,21 +520,16 @@
|
||||
};
|
||||
|
||||
document.getElementById('previewModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closePreview();
|
||||
}
|
||||
if (e.target === this) closePreview();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closePreview();
|
||||
}
|
||||
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 = '☀️';
|
||||
@@ -513,7 +537,6 @@
|
||||
document.body.classList.remove('dark-theme');
|
||||
button.textContent = '🌙';
|
||||
}
|
||||
|
||||
document.body.classList.add('loaded');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user