навел поряд в структуре
This commit is contained in:
61
403.html
61
403.html
@@ -1,61 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Trashbox - 403 Доступ запрещён</title>
|
|
||||||
<link rel="shortcut icon" href="/pic.svg" type="image/svg">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
background: #f8f9fa;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
background: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
.code {
|
|
||||||
font-size: 6rem;
|
|
||||||
font-weight: 700;
|
|
||||||
background: linear-gradient(135deg, #dc3545, #b02a37);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.icon { font-size: 4rem; margin: 0.5rem 0; }
|
|
||||||
h1 { font-size: 1.5rem; margin: 0.5rem 0; }
|
|
||||||
p { color: #666; margin: 0.5rem 0 1.5rem; }
|
|
||||||
a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.75rem 2rem;
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
a:hover { background: #5a6fd6; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="code">403</div>
|
|
||||||
<h1>Доступ запрещён</h1>
|
|
||||||
<p>Эй, дружок-пирожок, сюда нельзя</p>
|
|
||||||
<a href="/cgi-bin/index.cgi">Вернуться на главную</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
60
404.html
60
404.html
@@ -1,60 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Trashbox - 404 Страница не найдена</title>
|
|
||||||
<link rel="shortcut icon" href="/pic.svg" type="image/svg">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
background: #f8f9fa;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
text-align: center;
|
|
||||||
padding: 2rem;
|
|
||||||
background: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
.code {
|
|
||||||
font-size: 6rem;
|
|
||||||
font-weight: 700;
|
|
||||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.icon { font-size: 4rem; margin: 0.5rem 0; }
|
|
||||||
h1 { font-size: 1.5rem; margin: 0.5rem 0; }
|
|
||||||
p { color: #666; margin: 0.5rem 0 1.5rem; }
|
|
||||||
a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.75rem 2rem;
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
a:hover { background: #5a6fd6; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="code">404</div>
|
|
||||||
<h1>Страница не найдена</h1>
|
|
||||||
<p>Эй, дружок-пирожок, тобой выбрана неправильная дверь</p>
|
|
||||||
<a href="/cgi-bin/index.cgi">Вернуться на главную</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
25
Makefile
25
Makefile
@@ -1,25 +0,0 @@
|
|||||||
CC = gcc
|
|
||||||
CFLAGS = -Wall -Wextra -O2 -I./src
|
|
||||||
TARGETS = index.cgi style.cgi status.cgi
|
|
||||||
|
|
||||||
SRCS_INDEX = src/main.c src/config.c src/auth.c src/fs.c src/render.c src/utils.c
|
|
||||||
OBJS_INDEX = $(SRCS_INDEX:.c=.o)
|
|
||||||
|
|
||||||
all: $(TARGETS)
|
|
||||||
|
|
||||||
index.cgi: $(OBJS_INDEX)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^
|
|
||||||
|
|
||||||
style.cgi: src/style.c
|
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
|
||||||
|
|
||||||
status.cgi: src/status.c
|
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(OBJS_INDEX) $(TARGETS)
|
|
||||||
|
|
||||||
install: all
|
|
||||||
chmod +x $(TARGETS)
|
|
||||||
|
|
||||||
.PHONY: all clean install
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
http2 on;
|
|
||||||
server_name home.mashup.su www.home.mashup.su;
|
|
||||||
ssl_certificate /etc/letsencrypt/live/home.mashup.su/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/home.mashup.su/privkey.pem;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
|
||||||
|
|
||||||
root /home/romkazvo/www;
|
|
||||||
|
|
||||||
# === КАСТОМНЫЕ СТРАНИЦЫ ОШИБОК ===
|
|
||||||
error_page 404 =404 /404.html;
|
|
||||||
location = /404.html {
|
|
||||||
root /home/romkazvo/www;
|
|
||||||
allow all;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 403 =403 /403.html;
|
|
||||||
location = /403.html {
|
|
||||||
root /home/romkazvo/www;
|
|
||||||
allow all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# === СТАТИКА ===
|
|
||||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# === СТРИМ /gachi ===
|
|
||||||
location = /gachi {
|
|
||||||
proxy_pass http://127.0.0.1:8001/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_cache off;
|
|
||||||
proxy_read_timeout 3600;
|
|
||||||
proxy_send_timeout 3600;
|
|
||||||
add_header X-Stream-Name "Gachi Stream" always;
|
|
||||||
add_header Access-Control-Allow-Origin "*" always;
|
|
||||||
add_header Access-Control-Allow-Methods "GET, OPTIONS, HEAD" always;
|
|
||||||
add_header Access-Control-Allow-Headers "Range, Accept-Encoding" always;
|
|
||||||
}
|
|
||||||
|
|
||||||
# === CGI (с перехватом ошибок) ===
|
|
||||||
location /cgi-bin/ {
|
|
||||||
proxy_pass http://127.0.0.1:8050;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_cache off;
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_intercept_errors on;
|
|
||||||
}
|
|
||||||
|
|
||||||
# === ВСЁ ОСТАЛЬНОЕ ===
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:8050;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_cache off;
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_intercept_errors on;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTP → HTTPS
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name home.mashup.su www.home.mashup.su;
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
30
config.ini
30
config.ini
@@ -1,30 +0,0 @@
|
|||||||
# Trashbox CGI config
|
|
||||||
|
|
||||||
[paths]
|
|
||||||
base_path = /home/romkazvo/www
|
|
||||||
template_path = /home/romkazvo/www/cgi-bin/template.html
|
|
||||||
passwd_file = /home/romkazvo/www/cgi-bin/.htpasswd
|
|
||||||
|
|
||||||
[limits]
|
|
||||||
max_entries = 1000
|
|
||||||
|
|
||||||
[hidden]
|
|
||||||
dirs = cgi-bin,.git,.svn
|
|
||||||
files = .htpasswd,.htaccess,.gitignore, robots.txt, 404.html, 403.html
|
|
||||||
|
|
||||||
[preview]
|
|
||||||
image = jpg,jpeg,png,gif,webp,bmp,svg,ico
|
|
||||||
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
|
|
||||||
261
src/auth.c
261
src/auth.c
@@ -1,261 +0,0 @@
|
|||||||
#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) {
|
|
||||||
fprintf(stderr, "DEBUG: cannot open passwd file: %s\n", g_config.passwd_file);
|
|
||||||
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';
|
|
||||||
char *folder = line;
|
|
||||||
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 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int is_folder_protected(const char *folder_path) {
|
|
||||||
if (!folder_path || folder_path[0] == '\0') return 0;
|
|
||||||
|
|
||||||
FILE *f = fopen(g_config.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';
|
|
||||||
const char *last_slash = strrchr(folder_path, '/');
|
|
||||||
const char *folder_name = last_slash ? last_slash + 1 : folder_path;
|
|
||||||
if (strcmp(line, folder_path) == 0 || strcmp(line, folder_name) == 0) {
|
|
||||||
fclose(f);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
14
src/auth.h
14
src/auth.h
@@ -1,14 +0,0 @@
|
|||||||
#ifndef AUTH_H
|
|
||||||
#define AUTH_H
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
139
src/config.c
139
src/config.c
@@ -1,139 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
config_t g_config;
|
|
||||||
|
|
||||||
int load_config(const char *path) {
|
|
||||||
FILE *f = fopen(path, "r");
|
|
||||||
if (!f) return -1;
|
|
||||||
|
|
||||||
char line[512];
|
|
||||||
char current_section[64] = "";
|
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), f)) {
|
|
||||||
char *p = line;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
if (*p == '\0' || *p == '#' || *p == ';') continue;
|
|
||||||
|
|
||||||
if (*p == '[') {
|
|
||||||
p++;
|
|
||||||
char *end = strchr(p, ']');
|
|
||||||
if (end) {
|
|
||||||
*end = '\0';
|
|
||||||
strncpy(current_section, p, sizeof(current_section) - 1);
|
|
||||||
current_section[sizeof(current_section) - 1] = '\0';
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *eq = strchr(p, '=');
|
|
||||||
if (!eq) continue;
|
|
||||||
*eq = '\0';
|
|
||||||
char *key = p;
|
|
||||||
char *value = eq + 1;
|
|
||||||
|
|
||||||
char *end_key = key + strlen(key) - 1;
|
|
||||||
while (end_key > key && (*end_key == ' ' || *end_key == '\t')) {
|
|
||||||
*end_key = '\0';
|
|
||||||
end_key--;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (*value == ' ' || *value == '\t') value++;
|
|
||||||
char *end_val = value + strlen(value) - 1;
|
|
||||||
while (end_val > value && (*end_val == ' ' || *end_val == '\t' || *end_val == '\r' || *end_val == '\n')) {
|
|
||||||
*end_val = '\0';
|
|
||||||
end_val--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(current_section, "paths") == 0) {
|
|
||||||
if (strcmp(key, "base_path") == 0) strncpy(g_config.base_path, value, sizeof(g_config.base_path) - 1);
|
|
||||||
else if (strcmp(key, "template_path") == 0) strncpy(g_config.template_path, value, sizeof(g_config.template_path) - 1);
|
|
||||||
else if (strcmp(key, "passwd_file") == 0) strncpy(g_config.passwd_file, value, sizeof(g_config.passwd_file) - 1);
|
|
||||||
}
|
|
||||||
else if (strcmp(current_section, "limits") == 0) {
|
|
||||||
if (strcmp(key, "max_entries") == 0) g_config.max_entries = atoi(value);
|
|
||||||
}
|
|
||||||
else if (strcmp(current_section, "hidden") == 0) {
|
|
||||||
if (strcmp(key, "dirs") == 0) {
|
|
||||||
char *token = strtok(value, ",");
|
|
||||||
g_config.hidden_dirs_count = 0;
|
|
||||||
while (token && g_config.hidden_dirs_count < 10) {
|
|
||||||
while (*token == ' ') token++;
|
|
||||||
strncpy(g_config.hidden_dirs[g_config.hidden_dirs_count], token, 255);
|
|
||||||
g_config.hidden_dirs[g_config.hidden_dirs_count][255] = '\0';
|
|
||||||
g_config.hidden_dirs_count++;
|
|
||||||
token = strtok(NULL, ",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strcmp(key, "files") == 0) {
|
|
||||||
char *token = strtok(value, ",");
|
|
||||||
g_config.hidden_files_count = 0;
|
|
||||||
while (token && g_config.hidden_files_count < 10) {
|
|
||||||
while (*token == ' ') token++;
|
|
||||||
strncpy(g_config.hidden_files[g_config.hidden_files_count], token, 255);
|
|
||||||
g_config.hidden_files[g_config.hidden_files_count][255] = '\0';
|
|
||||||
g_config.hidden_files_count++;
|
|
||||||
token = strtok(NULL, ",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strcmp(current_section, "preview") == 0) {
|
|
||||||
if (strcmp(key, "image") == 0) strncpy(g_config.preview_image, value, sizeof(g_config.preview_image) - 1);
|
|
||||||
else if (strcmp(key, "text") == 0) strncpy(g_config.preview_text, value, sizeof(g_config.preview_text) - 1);
|
|
||||||
else if (strcmp(key, "audio") == 0) strncpy(g_config.preview_audio, value, sizeof(g_config.preview_audio) - 1);
|
|
||||||
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);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_default_config(void) {
|
|
||||||
strcpy(g_config.base_path, "/home/romkazvo/www");
|
|
||||||
strcpy(g_config.template_path, "/home/romkazvo/www/cgi-bin/template.html");
|
|
||||||
strcpy(g_config.passwd_file, "/home/romkazvo/www/cgi-bin/.htpasswd");
|
|
||||||
g_config.max_entries = 1000;
|
|
||||||
g_config.hidden_dirs_count = 0;
|
|
||||||
g_config.hidden_files_count = 0;
|
|
||||||
strcpy(g_config.preview_image, "jpg,jpeg,png,gif,webp,bmp,svg,ico");
|
|
||||||
strcpy(g_config.preview_text, "txt,md,html,htm,css,js,json,xml,csv");
|
|
||||||
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) {
|
|
||||||
if (!list || !*list) return 0;
|
|
||||||
char temp[512];
|
|
||||||
strncpy(temp, list, sizeof(temp) - 1);
|
|
||||||
temp[sizeof(temp) - 1] = '\0';
|
|
||||||
char *token = strtok(temp, ",");
|
|
||||||
while (token) {
|
|
||||||
while (*token == ' ') token++;
|
|
||||||
if (strcasecmp(token, str) == 0) return 1;
|
|
||||||
token = strtok(NULL, ",");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
32
src/config.h
32
src/config.h
@@ -1,32 +0,0 @@
|
|||||||
#ifndef CONFIG_H
|
|
||||||
#define CONFIG_H
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char base_path[1024];
|
|
||||||
char template_path[1024];
|
|
||||||
char passwd_file[1024];
|
|
||||||
int max_entries;
|
|
||||||
char hidden_dirs[10][256];
|
|
||||||
int hidden_dirs_count;
|
|
||||||
char hidden_files[10][256];
|
|
||||||
int hidden_files_count;
|
|
||||||
char preview_image[512];
|
|
||||||
char preview_text[512];
|
|
||||||
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;
|
|
||||||
|
|
||||||
int load_config(const char *path);
|
|
||||||
void set_default_config(void);
|
|
||||||
int is_string_in_list(const char *str, const char *list);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
179
src/fs.c
179
src/fs.c
@@ -1,179 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "fs.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "auth.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
query_params_t g_params;
|
|
||||||
|
|
||||||
int is_safe_path(const char *path) {
|
|
||||||
if (!path) return 0;
|
|
||||||
if (strstr(path, "..")) return 0;
|
|
||||||
if (strstr(path, "./")) return 0;
|
|
||||||
if (path[0] == '/') return 0;
|
|
||||||
if (strstr(path, "//")) return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void safe_path_join(char *result, size_t result_size, const char *base, const char *path) {
|
|
||||||
if (!is_safe_path(path)) {
|
|
||||||
snprintf(result, result_size, "%s", base);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snprintf(result, result_size, "%s/%s", base, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
int count_files_in_dir(const char *path) {
|
|
||||||
DIR *dir = opendir(path);
|
|
||||||
if (!dir) return 0;
|
|
||||||
struct dirent *entry;
|
|
||||||
int count = 0;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
if (entry->d_name[0] == '.') continue;
|
|
||||||
int hidden = 0;
|
|
||||||
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
if (hidden) continue;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
long long get_dir_size(const char *path) {
|
|
||||||
DIR *dir = opendir(path);
|
|
||||||
if (!dir) return 0;
|
|
||||||
struct dirent *entry;
|
|
||||||
struct stat statbuf;
|
|
||||||
char fullpath[1024];
|
|
||||||
long long size = 0;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
|
|
||||||
if (stat(fullpath, &statbuf) == 0) {
|
|
||||||
if (S_ISDIR(statbuf.st_mode)) size += get_dir_size(fullpath);
|
|
||||||
else if (S_ISREG(statbuf.st_mode)) size += statbuf.st_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* get_file_icon(const char* filename) {
|
|
||||||
const char *ext = strrchr(filename, '.');
|
|
||||||
if (!ext) return "📄";
|
|
||||||
ext++;
|
|
||||||
if (is_string_in_list(ext, g_config.preview_image)) return "🖼️";
|
|
||||||
if (is_string_in_list(ext, g_config.preview_audio)) return "🎵";
|
|
||||||
if (is_string_in_list(ext, g_config.preview_video)) return "🎬";
|
|
||||||
if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 ||
|
|
||||||
strcasecmp(ext, "7z") == 0 || strcasecmp(ext, "tar") == 0 ||
|
|
||||||
strcasecmp(ext, "gz") == 0) return "📦";
|
|
||||||
if (strcasecmp(ext, "pdf") == 0) return "📕";
|
|
||||||
if (strcasecmp(ext, "doc") == 0 || strcasecmp(ext, "docx") == 0) return "📘";
|
|
||||||
if (strcasecmp(ext, "xls") == 0 || strcasecmp(ext, "xlsx") == 0) return "📗";
|
|
||||||
if (strcasecmp(ext, "txt") == 0) return "📝";
|
|
||||||
return "📄";
|
|
||||||
}
|
|
||||||
|
|
||||||
int is_previewable(const char* filename) {
|
|
||||||
const char *ext = strrchr(filename, '.');
|
|
||||||
if (!ext) return 0;
|
|
||||||
ext++;
|
|
||||||
return (is_string_in_list(ext, g_config.preview_image) ||
|
|
||||||
is_string_in_list(ext, g_config.preview_text) ||
|
|
||||||
is_string_in_list(ext, g_config.preview_audio) ||
|
|
||||||
is_string_in_list(ext, g_config.preview_video) ||
|
|
||||||
is_string_in_list(ext, g_config.preview_pdf));
|
|
||||||
}
|
|
||||||
|
|
||||||
void search_recursive(const char *base_path, const char *display_path, const char *search_term, entry_t *results, int *count, int max_results) {
|
|
||||||
DIR *dir = opendir(base_path);
|
|
||||||
if (!dir) return;
|
|
||||||
struct dirent *entry;
|
|
||||||
struct stat file_stat;
|
|
||||||
char full_path[1024];
|
|
||||||
char display_subpath[1024];
|
|
||||||
while ((entry = readdir(dir)) != NULL && *count < max_results) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
if (entry->d_name[0] == '.') continue;
|
|
||||||
int hidden = 0;
|
|
||||||
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
if (hidden) continue;
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
|
||||||
if (display_path[0] == '\0') {
|
|
||||||
snprintf(display_subpath, sizeof(display_subpath), "%s", entry->d_name);
|
|
||||||
} else {
|
|
||||||
snprintf(display_subpath, sizeof(display_subpath), "%s/%s", display_path, entry->d_name);
|
|
||||||
}
|
|
||||||
if (is_folder_protected(display_subpath) || is_folder_protected(entry->d_name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (stat(full_path, &file_stat) == 0) {
|
|
||||||
if (my_strcasestr(entry->d_name, search_term) != NULL) {
|
|
||||||
entry_t *result = &results[*count];
|
|
||||||
snprintf(result->name, sizeof(result->name), "%s", entry->d_name);
|
|
||||||
if (S_ISDIR(file_stat.st_mode)) {
|
|
||||||
result->is_dir = 1;
|
|
||||||
result->size = 0;
|
|
||||||
result->dir_size = get_dir_size(full_path);
|
|
||||||
result->file_count = count_files_in_dir(full_path);
|
|
||||||
result->mtime = file_stat.st_mtime;
|
|
||||||
strcpy(result->icon, "📁");
|
|
||||||
result->is_protected = 0;
|
|
||||||
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
|
||||||
snprintf(result->file_url, sizeof(result->file_url), "%s", display_subpath);
|
|
||||||
(*count)++;
|
|
||||||
} else if (S_ISREG(file_stat.st_mode)) {
|
|
||||||
result->is_dir = 0;
|
|
||||||
result->size = file_stat.st_size;
|
|
||||||
result->dir_size = 0;
|
|
||||||
result->file_count = 0;
|
|
||||||
result->mtime = file_stat.st_mtime;
|
|
||||||
strcpy(result->icon, get_file_icon(entry->d_name));
|
|
||||||
result->is_protected = 0;
|
|
||||||
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
|
||||||
snprintf(result->file_url, sizeof(result->file_url), "%s", display_subpath);
|
|
||||||
(*count)++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (S_ISDIR(file_stat.st_mode)) {
|
|
||||||
search_recursive(full_path, display_subpath, search_term, results, count, max_results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
long long sizeB = entryB->is_dir ? entryB->dir_size : entryB->size;
|
|
||||||
result = (sizeA > sizeB) - (sizeA < sizeB);
|
|
||||||
} else if (strcmp(g_params.sort_by, "date") == 0) {
|
|
||||||
result = (entryA->mtime > entryB->mtime) - (entryA->mtime < entryB->mtime);
|
|
||||||
} else {
|
|
||||||
result = strcasecmp(entryA->name, entryB->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result * g_params.sort_order;
|
|
||||||
}
|
|
||||||
38
src/fs.h
38
src/fs.h
@@ -1,38 +0,0 @@
|
|||||||
#ifndef FS_H
|
|
||||||
#define FS_H
|
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char name[256];
|
|
||||||
int is_dir;
|
|
||||||
long size;
|
|
||||||
char icon[8];
|
|
||||||
char encoded_path[1024];
|
|
||||||
char file_url[2048];
|
|
||||||
int is_protected;
|
|
||||||
time_t mtime;
|
|
||||||
int file_count;
|
|
||||||
long long dir_size;
|
|
||||||
} entry_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char sort_by[16];
|
|
||||||
int sort_order;
|
|
||||||
char search[256];
|
|
||||||
int recursive;
|
|
||||||
char view[16];
|
|
||||||
} query_params_t;
|
|
||||||
|
|
||||||
extern query_params_t g_params;
|
|
||||||
|
|
||||||
int is_safe_path(const char *path);
|
|
||||||
void safe_path_join(char *result, size_t result_size, const char *base, const char *path);
|
|
||||||
int count_files_in_dir(const char *path);
|
|
||||||
long long get_dir_size(const char *path);
|
|
||||||
const char* get_file_icon(const char* filename);
|
|
||||||
int is_previewable(const char* filename);
|
|
||||||
void search_recursive(const char *base_path, const char *display_path, const char *search_term, entry_t *results, int *count, int max_results);
|
|
||||||
int compare_entries_sorted(const void *a, const void *b);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
245
src/main.c
245
src/main.c
@@ -1,245 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <locale.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include "config.h"
|
|
||||||
#include "auth.h"
|
|
||||||
#include "fs.h"
|
|
||||||
#include "render.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
setlocale(LC_ALL, "en_US.UTF-8");
|
|
||||||
setlocale(LC_CTYPE, "en_US.UTF-8");
|
|
||||||
|
|
||||||
if (load_config("/home/romkazvo/www/cgi-bin/config.ini") != 0) {
|
|
||||||
set_default_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
strcpy(g_params.sort_by, "name");
|
|
||||||
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) {
|
|
||||||
path_start += 5;
|
|
||||||
char *path_end = strchr(path_start, '&');
|
|
||||||
int path_len = path_end ? (int)(path_end - path_start) : (int)strlen(path_start);
|
|
||||||
if (path_len > 0 && path_len < (int)sizeof(display_path) - 1) {
|
|
||||||
char encoded_path[1024];
|
|
||||||
strncpy(encoded_path, path_start, path_len);
|
|
||||||
encoded_path[path_len] = '\0';
|
|
||||||
url_decode_enhanced(encoded_path, display_path, sizeof(display_path));
|
|
||||||
if (!is_safe_path(display_path)) {
|
|
||||||
display_path[0] = '\0';
|
|
||||||
strcpy(base_path, g_config.base_path);
|
|
||||||
} else {
|
|
||||||
strncpy(safe_display_path, display_path, sizeof(safe_display_path) - 1);
|
|
||||||
safe_display_path[sizeof(safe_display_path) - 1] = '\0';
|
|
||||||
safe_path_join(base_path, sizeof(base_path), g_config.base_path, safe_display_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *sort_start = strstr(query_string, "sort=");
|
|
||||||
if (sort_start) {
|
|
||||||
sort_start += 5;
|
|
||||||
char *sort_end = strchr(sort_start, '&');
|
|
||||||
int sort_len = sort_end ? (int)(sort_end - sort_start) : (int)strlen(sort_start);
|
|
||||||
if (sort_len > 0 && sort_len < (int)sizeof(g_params.sort_by)) {
|
|
||||||
strncpy(g_params.sort_by, sort_start, sort_len);
|
|
||||||
g_params.sort_by[sort_len] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
char *search_end = strchr(search_start, '&');
|
|
||||||
int search_len = search_end ? (int)(search_end - search_start) : (int)strlen(search_start);
|
|
||||||
if (search_len > 0 && search_len < (int)sizeof(g_params.search) - 1) {
|
|
||||||
char encoded_search[256];
|
|
||||||
strncpy(encoded_search, search_start, search_len);
|
|
||||||
encoded_search[search_len] = '\0';
|
|
||||||
url_decode_enhanced(encoded_search, g_params.search, sizeof(g_params.search));
|
|
||||||
if (g_params.search[0] != '\0') {
|
|
||||||
g_params.recursive = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
print_template(base_path, display_path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
657
src/render.c
657
src/render.c
@@ -1,657 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include "render.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "auth.h"
|
|
||||||
#include "fs.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
extern query_params_t g_params;
|
|
||||||
|
|
||||||
void buffer_init(buffer_t *buf) {
|
|
||||||
buf->capacity = 65536;
|
|
||||||
buf->data = malloc(buf->capacity);
|
|
||||||
buf->size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void buffer_append(buffer_t *buf, const char *str) {
|
|
||||||
size_t len = strlen(str);
|
|
||||||
if (buf->size + len >= buf->capacity) {
|
|
||||||
buf->capacity *= 2;
|
|
||||||
buf->data = realloc(buf->data, buf->capacity);
|
|
||||||
}
|
|
||||||
memcpy(buf->data + buf->size, str, len);
|
|
||||||
buf->size += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
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];
|
|
||||||
char *path_copy = strdup(display_path);
|
|
||||||
char *token = strtok(path_copy, "/");
|
|
||||||
while (token) {
|
|
||||||
buffer_append(buf, " / ");
|
|
||||||
if (temp_path[0]) strcat(temp_path, "/");
|
|
||||||
strcat(temp_path, token);
|
|
||||||
url_encode(temp_path, encoded, sizeof(encoded));
|
|
||||||
char safe_token[512];
|
|
||||||
html_escape(token, safe_token, sizeof(safe_token));
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
buffer_append(buf, "\n</div>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
buffer_append(buf, " <h2>Папка защищена паролем</h2>\n");
|
|
||||||
buffer_append(buf, " <p>Введите пароль для доступа к папке <strong>");
|
|
||||||
char safe_name[512];
|
|
||||||
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=\"POST\" 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=\"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, "❌ Неверный пароль! Попробуйте снова.");
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
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 = 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 *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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!is_root && !has_valid_token && display_path && display_path[0]) {
|
|
||||||
char *last_slash = strrchr(display_path, '/');
|
|
||||||
char *folder_name = last_slash ? last_slash + 1 : (char*)display_path;
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
if (result_count == 0) {
|
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
|
||||||
buffer_append(buf, " <div class=\"icon\">🔍</div>\n");
|
|
||||||
buffer_append(buf, " <h3>Ничего не найдено</h3>\n");
|
|
||||||
buffer_append(buf, " <p>По запросу \"");
|
|
||||||
char safe_search[512];
|
|
||||||
html_escape(g_params.search, safe_search, sizeof(safe_search));
|
|
||||||
buffer_append(buf, safe_search);
|
|
||||||
buffer_append(buf, "\" ничего не найдено</p>\n");
|
|
||||||
buffer_append(buf, "</div>\n");
|
|
||||||
free(entries);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entry_count = result_count;
|
|
||||||
is_recursive_search = 1;
|
|
||||||
for (int i = 0; i < entry_count; i++) {
|
|
||||||
if (entries[i].is_dir) {
|
|
||||||
dir_count++;
|
|
||||||
total_size += entries[i].dir_size;
|
|
||||||
} else {
|
|
||||||
file_count++;
|
|
||||||
total_size += entries[i].size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dir = opendir(base_path);
|
|
||||||
if (!dir) {
|
|
||||||
char alt_path[1024];
|
|
||||||
snprintf(alt_path, sizeof(alt_path), "%s", g_config.base_path);
|
|
||||||
if (display_path[0]) {
|
|
||||||
char *encoded = strdup(display_path);
|
|
||||||
url_decode_enhanced(encoded, alt_path + strlen(alt_path), sizeof(alt_path) - strlen(alt_path));
|
|
||||||
free(encoded);
|
|
||||||
}
|
|
||||||
dir = opendir(alt_path);
|
|
||||||
if (!dir) {
|
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
|
||||||
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, display_base);
|
|
||||||
buffer_append(buf, "</h3>\n");
|
|
||||||
buffer_append(buf, " <p>Папка не найдена</p>\n");
|
|
||||||
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
|
|
||||||
buffer_append(buf, "</div>\n");
|
|
||||||
free(entries);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ((entry = readdir(dir)) != NULL && entry_count < g_config.max_entries) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
int hidden = 0;
|
|
||||||
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
if (entry->d_name[0] == '.') hidden = 1;
|
|
||||||
if (hidden) continue;
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
|
||||||
if (stat(full_path, &file_stat) == 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;
|
|
||||||
entries[entry_count].mtime = file_stat.st_mtime;
|
|
||||||
entries[entry_count].file_count = count_files_in_dir(full_path);
|
|
||||||
entries[entry_count].dir_size = get_dir_size(full_path);
|
|
||||||
total_size += entries[entry_count].dir_size;
|
|
||||||
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];
|
|
||||||
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
|
||||||
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
|
|
||||||
dir_count++;
|
|
||||||
} else if (S_ISREG(file_stat.st_mode)) {
|
|
||||||
entries[entry_count].is_dir = 0;
|
|
||||||
entries[entry_count].size = file_stat.st_size;
|
|
||||||
entries[entry_count].mtime = file_stat.st_mtime;
|
|
||||||
entries[entry_count].file_count = 0;
|
|
||||||
entries[entry_count].dir_size = 0;
|
|
||||||
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
|
|
||||||
entries[entry_count].is_protected = 0;
|
|
||||||
char encoded_filename[1024];
|
|
||||||
url_encode(entry->d_name, encoded_filename, sizeof(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 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
entry_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
if (entry_count == 0) {
|
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
|
||||||
buffer_append(buf, " <div class=\"icon\">📄</div>\n");
|
|
||||||
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
|
|
||||||
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
|
|
||||||
buffer_append(buf, "</div>\n");
|
|
||||||
free(entries);
|
|
||||||
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");
|
|
||||||
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=\"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&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&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&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, "🔍 Результаты поиска по всему сайту: ");
|
|
||||||
char tmp[32];
|
|
||||||
snprintf(tmp, sizeof(tmp), "%d", entry_count);
|
|
||||||
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; }
|
|
||||||
if (has_dirs) {
|
|
||||||
int display_dir_count = 0;
|
|
||||||
for (int i = 0; i < entry_count; i++) if (entries[i].is_dir) display_dir_count++;
|
|
||||||
char section_title[128];
|
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", display_dir_count);
|
|
||||||
buffer_append(buf, section_title);
|
|
||||||
for (int i = 0; i < entry_count; i++) {
|
|
||||||
if (entries[i].is_dir) {
|
|
||||||
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, "', '");
|
|
||||||
char safe_name[512];
|
|
||||||
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) {
|
|
||||||
char safe_url[512];
|
|
||||||
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
|
||||||
buffer_append(buf, safe_url);
|
|
||||||
} else {
|
|
||||||
char safe_name2[512];
|
|
||||||
html_escape(entries[i].name, safe_name2, sizeof(safe_name2));
|
|
||||||
buffer_append(buf, safe_name2);
|
|
||||||
}
|
|
||||||
buffer_append(buf, " <span style=\"font-size:0.9rem;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, "', '");
|
|
||||||
char safe_name3[512];
|
|
||||||
html_escape(entries[i].name, safe_name3, sizeof(safe_name3));
|
|
||||||
buffer_append(buf, safe_name3);
|
|
||||||
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");
|
|
||||||
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");
|
|
||||||
buffer_append(buf, " <span class=\"file-name\">");
|
|
||||||
if (is_recursive_search) {
|
|
||||||
char safe_url[512];
|
|
||||||
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
|
||||||
buffer_append(buf, safe_url);
|
|
||||||
} else {
|
|
||||||
char safe_name4[512];
|
|
||||||
html_escape(entries[i].name, safe_name4, sizeof(safe_name4));
|
|
||||||
buffer_append(buf, safe_name4);
|
|
||||||
}
|
|
||||||
buffer_append(buf, "</span>\n");
|
|
||||||
buffer_append(buf, " </a>\n");
|
|
||||||
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];
|
|
||||||
format_size(entries[i].dir_size, size_str);
|
|
||||||
buffer_append(buf, " <span class=\"file-meta size\">");
|
|
||||||
buffer_append(buf, size_str);
|
|
||||||
buffer_append(buf, "</span>\n");
|
|
||||||
}
|
|
||||||
buffer_append(buf, " </div>\n");
|
|
||||||
buffer_append(buf, " </div>\n");
|
|
||||||
}
|
|
||||||
buffer_append(buf, " </li>\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int has_files = 0;
|
|
||||||
for (int i = 0; i < entry_count; i++) if (!entries[i].is_dir) { has_files = 1; break; }
|
|
||||||
if (has_files) {
|
|
||||||
int display_file_count = 0;
|
|
||||||
for (int i = 0; i < entry_count; i++) if (!entries[i].is_dir) display_file_count++;
|
|
||||||
char section_title[128];
|
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", display_file_count);
|
|
||||||
buffer_append(buf, section_title);
|
|
||||||
for (int i = 0; i < entry_count; i++) {
|
|
||||||
if (!entries[i].is_dir) {
|
|
||||||
char size_str[32];
|
|
||||||
char date_str[64];
|
|
||||||
format_size(entries[i].size, size_str);
|
|
||||||
format_date(entries[i].mtime, date_str, sizeof(date_str));
|
|
||||||
buffer_append(buf, " <li>\n");
|
|
||||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
|
||||||
char file_link[2048];
|
|
||||||
if (is_recursive_search) {
|
|
||||||
snprintf(file_link, sizeof(file_link), "/%s", entries[i].file_url);
|
|
||||||
} else {
|
|
||||||
snprintf(file_link, sizeof(file_link), "/%s", entries[i].file_url);
|
|
||||||
}
|
|
||||||
buffer_append(buf, " <a class=\"file-link\" href=\"");
|
|
||||||
buffer_append(buf, file_link);
|
|
||||||
buffer_append(buf, "\" download>\n");
|
|
||||||
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\">");
|
|
||||||
if (is_recursive_search) {
|
|
||||||
char safe_url[512];
|
|
||||||
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
|
||||||
buffer_append(buf, safe_url);
|
|
||||||
} else {
|
|
||||||
char safe_name5[512];
|
|
||||||
html_escape(entries[i].name, safe_name5, sizeof(safe_name5));
|
|
||||||
buffer_append(buf, safe_name5);
|
|
||||||
}
|
|
||||||
buffer_append(buf, "</span>\n");
|
|
||||||
buffer_append(buf, " </a>\n");
|
|
||||||
buffer_append(buf, " <div class=\"file-controls\">\n");
|
|
||||||
if (is_previewable(entries[i].name)) {
|
|
||||||
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('");
|
|
||||||
buffer_append(buf, file_link);
|
|
||||||
buffer_append(buf, "')\" title=\"Открыть в браузере\">👁</button>\n");
|
|
||||||
}
|
|
||||||
buffer_append(buf, " <button class=\"download-btn\" onclick=\"downloadFile('");
|
|
||||||
buffer_append(buf, file_link);
|
|
||||||
buffer_append(buf, "')\" title=\"Скачать файл\">⬇️</button>\n");
|
|
||||||
buffer_append(buf, " <button class=\"copy-btn\" onclick=\"copyLink('");
|
|
||||||
buffer_append(buf, file_link);
|
|
||||||
buffer_append(buf, "')\" title=\"Копировать ссылку на файл\">🔗</button>\n");
|
|
||||||
buffer_append(buf, " <span class=\"file-meta date\">");
|
|
||||||
buffer_append(buf, date_str);
|
|
||||||
buffer_append(buf, "</span>\n");
|
|
||||||
buffer_append(buf, " <span class=\"file-meta size\">");
|
|
||||||
buffer_append(buf, size_str);
|
|
||||||
buffer_append(buf, "</span>\n");
|
|
||||||
buffer_append(buf, " </div>\n");
|
|
||||||
buffer_append(buf, " </div>\n");
|
|
||||||
buffer_append(buf, " </li>\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer_append(buf, "</ul>\n");
|
|
||||||
if (is_recursive_search) {
|
|
||||||
char stats[256];
|
|
||||||
snprintf(stats, sizeof(stats),
|
|
||||||
"<div class=\"stats\">\n Найдено: папок %d | файлов %d\n</div>\n",
|
|
||||||
dir_count, file_count);
|
|
||||||
buffer_append(buf, stats);
|
|
||||||
} else {
|
|
||||||
char total_size_str[32];
|
|
||||||
format_size(total_size, total_size_str);
|
|
||||||
char stats[256];
|
|
||||||
snprintf(stats, sizeof(stats),
|
|
||||||
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
|
|
||||||
dir_count, file_count, total_size_str);
|
|
||||||
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];
|
|
||||||
while (fgets(line, sizeof(line), file)) {
|
|
||||||
if (strstr(line, "<!--BREADCRUMB-->")) {
|
|
||||||
print_breadcrumb(&output, display_path);
|
|
||||||
} else if (strstr(line, "<!--CONTENT-->")) {
|
|
||||||
print_content(&output, base_path, display_path);
|
|
||||||
} else {
|
|
||||||
buffer_append(&output, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
18
src/render.h
18
src/render.h
@@ -1,18 +0,0 @@
|
|||||||
#ifndef RENDER_H
|
|
||||||
#define RENDER_H
|
|
||||||
|
|
||||||
#include "fs.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
size_t capacity;
|
|
||||||
} buffer_t;
|
|
||||||
|
|
||||||
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
|
|
||||||
97
src/status.c
97
src/status.c
@@ -1,97 +0,0 @@
|
|||||||
// /home/romkazvo/www/cgi-bin/src/status.c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/resource.h>
|
|
||||||
|
|
||||||
long get_nginx_memory() {
|
|
||||||
long total_kb = 0;
|
|
||||||
char command[] = "ps -o rss= -C nginx 2>/dev/null";
|
|
||||||
|
|
||||||
FILE *ps = popen(command, "r");
|
|
||||||
if (!ps) return 0;
|
|
||||||
|
|
||||||
char line[64];
|
|
||||||
while (fgets(line, sizeof(line), ps)) {
|
|
||||||
long kb;
|
|
||||||
if (sscanf(line, "%ld", &kb) == 1) {
|
|
||||||
total_kb += kb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pclose(ps);
|
|
||||||
|
|
||||||
return total_kb;
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_process_memory(const char *process_name, long *memory_kb) {
|
|
||||||
char command[256];
|
|
||||||
snprintf(command, sizeof(command), "ps -o rss= -C %s 2>/dev/null | head -1", process_name);
|
|
||||||
|
|
||||||
FILE *ps = popen(command, "r");
|
|
||||||
if (!ps) return 0;
|
|
||||||
|
|
||||||
int result = fscanf(ps, "%ld", memory_kb);
|
|
||||||
pclose(ps);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
36
src/style.c
36
src/style.c
@@ -1,36 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
char css_path[1024] = "/home/romkazvo/www/cgi-bin/style.css";
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
if (stat(css_path, &st) != 0) {
|
|
||||||
printf("Status: 404 Not Found\n");
|
|
||||||
printf("Content-type: text/plain\n\n");
|
|
||||||
printf("CSS file not found\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Content-type: text/css\n");
|
|
||||||
printf("Cache-Control: public, max-age=3600\n\n");
|
|
||||||
|
|
||||||
FILE *css_file = fopen(css_path, "r");
|
|
||||||
if (!css_file) {
|
|
||||||
printf("Status: 500 Internal Server Error\n");
|
|
||||||
printf("Content-type: text/plain\n\n");
|
|
||||||
printf("Cannot open CSS file\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buffer[4096];
|
|
||||||
size_t bytes_read;
|
|
||||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), css_file)) > 0) {
|
|
||||||
fwrite(buffer, 1, bytes_read, stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(css_file);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
98
src/utils.c
98
src/utils.c
@@ -1,98 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
char* my_strcasestr(const char *haystack, const char *needle) {
|
|
||||||
if (!haystack || !needle || *needle == '\0') return (char*)haystack;
|
|
||||||
size_t needle_len = strlen(needle);
|
|
||||||
while (*haystack) {
|
|
||||||
if (strncasecmp(haystack, needle, needle_len) == 0) return (char*)haystack;
|
|
||||||
haystack++;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void html_escape(const char *src, char *dst, size_t dst_size) {
|
|
||||||
if (!src || !dst || dst_size == 0) return;
|
|
||||||
char *p = dst;
|
|
||||||
size_t i = 0;
|
|
||||||
while (*src && i < dst_size - 6) {
|
|
||||||
switch (*src) {
|
|
||||||
case '<': strcpy(p, "<"); p += 4; i += 4; break;
|
|
||||||
case '>': strcpy(p, ">"); p += 4; i += 4; break;
|
|
||||||
case '"': strcpy(p, """); p += 6; i += 6; break;
|
|
||||||
case '&': strcpy(p, "&"); p += 5; i += 5; break;
|
|
||||||
default: *p++ = *src; i++; break;
|
|
||||||
}
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
*p = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void format_size(long long size, char* buffer) {
|
|
||||||
if (size < 1024) snprintf(buffer, 32, "%lld B", size);
|
|
||||||
else if (size < 1024 * 1024) snprintf(buffer, 32, "%.1f KB", size / 1024.0);
|
|
||||||
else if (size < 1024 * 1024 * 1024) snprintf(buffer, 32, "%.1f MB", size / (1024.0 * 1024.0));
|
|
||||||
else snprintf(buffer, 32, "%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void format_date(time_t mtime, char* buffer, size_t size) {
|
|
||||||
struct tm *tm_info = localtime(&mtime);
|
|
||||||
strftime(buffer, size, "%d.%m.%Y", tm_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
void url_encode(const char *src, char *dst, size_t dst_size) {
|
|
||||||
static const char *hex = "0123456789ABCDEF";
|
|
||||||
char *p = dst;
|
|
||||||
size_t i = 0;
|
|
||||||
while (*src && i < dst_size - 3) {
|
|
||||||
unsigned char c = (unsigned char)*src;
|
|
||||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= '0' && c <= '9') || strchr("-_.~", c)) {
|
|
||||||
*p++ = c; i++;
|
|
||||||
} else if (c == ' ') {
|
|
||||||
*p++ = '%'; *p++ = '2'; *p++ = '0'; i += 3;
|
|
||||||
} else {
|
|
||||||
*p++ = '%'; *p++ = hex[(c >> 4) & 0xF]; *p++ = hex[c & 0xF]; i += 3;
|
|
||||||
}
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
*p = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void url_decode_enhanced(const char *src, char *dst, size_t dst_size) {
|
|
||||||
char *p = dst;
|
|
||||||
size_t decoded_len = 0;
|
|
||||||
while (*src && decoded_len < dst_size - 1) {
|
|
||||||
if (*src == '%') {
|
|
||||||
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
|
|
||||||
char hex[3] = {src[1], src[2], '\0'};
|
|
||||||
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
|
|
||||||
if (c >= 0x80 || c >= 0x20 || c == 0x0A || c == 0x0D) {
|
|
||||||
*p++ = c;
|
|
||||||
decoded_len++;
|
|
||||||
} else {
|
|
||||||
*p++ = '_';
|
|
||||||
decoded_len++;
|
|
||||||
}
|
|
||||||
src += 3;
|
|
||||||
} else {
|
|
||||||
// ============================================
|
|
||||||
// ФИКС: если % не валидный — просто пропускаем его
|
|
||||||
// ============================================
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
} else if (*src == '+') {
|
|
||||||
*p++ = ' ';
|
|
||||||
decoded_len++;
|
|
||||||
src++;
|
|
||||||
} else {
|
|
||||||
*p++ = *src++;
|
|
||||||
decoded_len++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*p = '\0';
|
|
||||||
}
|
|
||||||
13
src/utils.h
13
src/utils.h
@@ -1,13 +0,0 @@
|
|||||||
#ifndef UTILS_H
|
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
void url_encode(const char *src, char *dst, size_t dst_size);
|
|
||||||
void url_decode_enhanced(const char *src, char *dst, size_t dst_size);
|
|
||||||
void html_escape(const char *src, char *dst, size_t dst_size);
|
|
||||||
char* my_strcasestr(const char *haystack, const char *needle);
|
|
||||||
void format_size(long long size, char* buffer);
|
|
||||||
void format_date(time_t mtime, char* buffer, size_t size);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
546
template.html
546
template.html
@@ -1,546 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
|
||||||
<title>Trashbox - личная помоечка</title>
|
|
||||||
<link rel="shortcut icon" href="/pic.svg" type="image/svg">
|
|
||||||
<link rel="stylesheet" href="/cgi-bin/style.cgi">
|
|
||||||
<meta property="og:image" content="https://home.mashup.su/og.png" />
|
|
||||||
<meta property="og:image:width" content="600" />
|
|
||||||
<meta property="og:image:height" content="315" />
|
|
||||||
<meta property="og:locale" content="ru_RU" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:title" content="Файлопомойка от RomkaZVO" />
|
|
||||||
<meta property="og:description" content="Моя личная помоечка" />
|
|
||||||
<meta property="og:url" content="https://home.mashup.su/" />
|
|
||||||
<meta property="og:site_name" content="Помойка от RomkaZVO" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:title" content="Файлопомойка от RomkaZVO" />
|
|
||||||
<meta name="twitter:description" content="Моя личная помоечка" />
|
|
||||||
<meta name="twitter:image" content="https://home.mashup.su/og.png" />
|
|
||||||
<meta name="description" content="Моя личная помоечка" />
|
|
||||||
<meta name="keywords" content="RomkaZVO, mashup, mashup su, файлы, обмен, скачать, загрузить" />
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function getCookie(name) {
|
|
||||||
const value = `; ${document.cookie}`;
|
|
||||||
const parts = value.split(`; ${name}=`);
|
|
||||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
const savedTheme = getCookie('theme');
|
|
||||||
if (savedTheme === 'dark') {
|
|
||||||
document.documentElement.classList.add('dark-theme');
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.preview-btn,
|
|
||||||
.copy-btn,
|
|
||||||
.download-btn {
|
|
||||||
background: transparent !important;
|
|
||||||
color: var(--text-secondary) !important;
|
|
||||||
border: 1px solid var(--border-color) !important;
|
|
||||||
border-radius: 6px !important;
|
|
||||||
width: 32px !important;
|
|
||||||
height: 32px !important;
|
|
||||||
cursor: pointer !important;
|
|
||||||
font-size: 0.85rem !important;
|
|
||||||
display: inline-flex !important;
|
|
||||||
align-items: center !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
opacity: 0.7 !important;
|
|
||||||
flex-shrink: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-btn:hover,
|
|
||||||
.copy-btn:hover,
|
|
||||||
.download-btn:hover {
|
|
||||||
background: var(--accent-blue) !important;
|
|
||||||
color: white !important;
|
|
||||||
border-color: var(--accent-blue) !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
transform: scale(1.05) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#copyNotification {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 80px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background: var(--accent-green);
|
|
||||||
color: white;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
||||||
z-index: 3000;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.preview-btn,
|
|
||||||
.copy-btn,
|
|
||||||
.download-btn {
|
|
||||||
width: 28px !important;
|
|
||||||
height: 28px !important;
|
|
||||||
font-size: 0.75rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:center;gap:0.0002rem;flex-wrap:wrap;">
|
|
||||||
<img src="/pic.svg" alt="Trashbox" style="max-width:120px;height:auto;border-radius:12px;flex-shrink:0;">
|
|
||||||
<div>
|
|
||||||
<h1 style="margin:0;">Trashbox</h1>
|
|
||||||
<div style="opacity:0.9;font-size:1.1rem;margin-top:0.2rem;">
|
|
||||||
Склад всякой всячины
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:center;padding:0.5rem 1rem;font-size:1.1rem;color:var(--text-secondary);border-bottom:1px solid var(--border-color);background:var(--bg-primary);">
|
|
||||||
<span style="opacity:0.7;">Личный уголок для обмена файлами. Заливаю сюда то, чем хочу поделиться, без всяких лимитов и сторонних сервисов.</span>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<!--BREADCRUMB-->
|
|
||||||
<!--CONTENT-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<div class="footer-content">
|
|
||||||
<span>Навайбкодил с любовью ❤️</span>
|
|
||||||
<a href="https://romkazvo.ru" target="_blank" class="footer-link">RomkaZVO</a><br>
|
|
||||||
Работает на BusyBox httpd и Nginx для SSL<br>
|
|
||||||
Исходники <a href="https://git.mashup.su/RomkaZVO/Trashbox" target="_blank" class="footer-link">здесь</a><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="status" style="margin-top: 1rem; font-size: 0.8rem; opacity: 0.7; line-height: 1.4;">
|
|
||||||
Загрузка статистики...
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
const statusEl = document.getElementById('status');
|
|
||||||
if (!statusEl) return;
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
fetch('/cgi-bin/status.cgi')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => {
|
|
||||||
statusEl.innerHTML = data.replace(/\n/g, '<br>');
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
statusEl.innerHTML = 'Ошибка загрузки статуса';
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<button class="scroll-top" onclick="scrollToTop()">↑</button>
|
|
||||||
|
|
||||||
<div id="previewModal" class="preview-modal">
|
|
||||||
<div class="preview-content" id="previewContent">
|
|
||||||
<div class="preview-header">
|
|
||||||
<h3 class="preview-title" id="previewTitle">Предпросмотр файла</h3>
|
|
||||||
<button class="close-preview" onclick="closePreview()">×</button>
|
|
||||||
</div>
|
|
||||||
<iframe id="previewFrame" class="preview-iframe" sandbox="allow-scripts allow-same-origin"></iframe>
|
|
||||||
</div>
|
|
||||||
</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');
|
|
||||||
const frame = document.getElementById('previewFrame');
|
|
||||||
|
|
||||||
if (!modal || !content || !title || !frame) {
|
|
||||||
console.error('Modal elements not found!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.style.display = 'none';
|
|
||||||
title.textContent = '🔒 Введите пароль для папки: ' + folderName;
|
|
||||||
|
|
||||||
const oldContainer = document.getElementById('passwordFormContainer');
|
|
||||||
if (oldContainer) oldContainer.remove();
|
|
||||||
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.id = 'passwordFormContainer';
|
|
||||||
container.style.cssText = 'display:flex;justify-content:center;align-items:center;padding:2rem;flex-grow:1;min-height:300px;';
|
|
||||||
|
|
||||||
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);">
|
|
||||||
<div style="font-size:3rem;margin-bottom:1rem;">🔒</div>
|
|
||||||
<h2 style="font-size:1.3rem;margin-bottom:0.5rem;color:var(--text-primary);">Защищенная папка</h2>
|
|
||||||
<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="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>
|
|
||||||
</form>
|
|
||||||
<p class="error-msg" style="color:#dc3545;font-size:0.9rem;min-height:1.5rem;margin:0.5rem 0;"></p>
|
|
||||||
<button onclick="closePreview()" style="background:transparent;border:none;color:var(--text-secondary);cursor:pointer;font-size:0.9rem;margin-top:0.5rem;">✖ Закрыть</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
content.appendChild(container);
|
|
||||||
content.style.width = '500px';
|
|
||||||
content.style.height = 'auto';
|
|
||||||
content.style.maxWidth = '90vw';
|
|
||||||
content.style.maxHeight = 'auto';
|
|
||||||
content.style.margin = 'auto';
|
|
||||||
content.style.position = 'absolute';
|
|
||||||
content.style.left = '50%';
|
|
||||||
content.style.top = '50%';
|
|
||||||
content.style.transform = 'translate(-50%, -50%)';
|
|
||||||
|
|
||||||
modal.style.display = 'block';
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const input = container.querySelector('input[type="password"]');
|
|
||||||
if (input) input.focus();
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.validatePasswordForm = function(form) {
|
|
||||||
const password = form.querySelector('input[type="password"]').value;
|
|
||||||
const errorMsg = form.parentElement.querySelector('.error-msg');
|
|
||||||
if (!password || password.length < 1) {
|
|
||||||
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 = cleanPath;
|
|
||||||
link.download = fileName;
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.copyLink = function(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('✅ Ссылка скопирована!');
|
|
||||||
}).catch(function() {
|
|
||||||
fallbackCopy(url);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fallbackCopy(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function fallbackCopy(text) {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.value = text;
|
|
||||||
textarea.style.position = 'fixed';
|
|
||||||
textarea.style.left = '-9999px';
|
|
||||||
textarea.style.top = '-9999px';
|
|
||||||
document.body.appendChild(textarea);
|
|
||||||
textarea.select();
|
|
||||||
try {
|
|
||||||
document.execCommand('copy');
|
|
||||||
showNotification('✅ Ссылка скопирована!');
|
|
||||||
} catch (e) {
|
|
||||||
alert('Не удалось скопировать ссылку');
|
|
||||||
}
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNotification(msg) {
|
|
||||||
const old = document.getElementById('copyNotification');
|
|
||||||
if (old) old.remove();
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.id = 'copyNotification';
|
|
||||||
div.textContent = msg;
|
|
||||||
document.body.appendChild(div);
|
|
||||||
setTimeout(() => {
|
|
||||||
div.style.opacity = '0';
|
|
||||||
setTimeout(() => div.remove(), 400);
|
|
||||||
}, 2500);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.scrollToTop = function() {
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('scroll', function() {
|
|
||||||
const scrollBtn = document.querySelector('.scroll-top');
|
|
||||||
if (window.scrollY > 300) {
|
|
||||||
scrollBtn.classList.add('visible');
|
|
||||||
} else {
|
|
||||||
scrollBtn.classList.remove('visible');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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=/';
|
|
||||||
};
|
|
||||||
|
|
||||||
window.openPreview = function(fileUrl) {
|
|
||||||
const modal = document.getElementById('previewModal');
|
|
||||||
const frame = document.getElementById('previewFrame');
|
|
||||||
const content = document.getElementById('previewContent');
|
|
||||||
const title = document.getElementById('previewTitle');
|
|
||||||
|
|
||||||
const oldContainer = document.getElementById('passwordFormContainer');
|
|
||||||
if (oldContainer) oldContainer.remove();
|
|
||||||
|
|
||||||
frame.style.display = 'block';
|
|
||||||
const fileName = fileUrl.split('/').pop();
|
|
||||||
const decodedFileName = decodeURIComponent(fileName);
|
|
||||||
title.textContent = decodedFileName;
|
|
||||||
|
|
||||||
const fileExt = fileName.split('.').pop().toLowerCase();
|
|
||||||
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico'];
|
|
||||||
const textTypes = ['txt', 'md', 'html', 'htm', 'css', 'js', 'json', 'xml', 'csv'];
|
|
||||||
const audioTypes = ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac'];
|
|
||||||
const videoTypes = ['mp4', 'webm', 'ogv', 'mov', 'avi', 'mkv'];
|
|
||||||
const isPDF = fileExt === 'pdf';
|
|
||||||
|
|
||||||
frame.src = '';
|
|
||||||
|
|
||||||
let imgContainer = document.getElementById('previewImageContainer');
|
|
||||||
if (!imgContainer) {
|
|
||||||
imgContainer = document.createElement('div');
|
|
||||||
imgContainer.id = 'previewImageContainer';
|
|
||||||
imgContainer.className = 'preview-image-container';
|
|
||||||
content.appendChild(imgContainer);
|
|
||||||
}
|
|
||||||
imgContainer.style.display = 'none';
|
|
||||||
imgContainer.innerHTML = '';
|
|
||||||
|
|
||||||
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.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';
|
|
||||||
content.style.maxHeight = 'none';
|
|
||||||
content.style.margin = '2% auto';
|
|
||||||
content.style.position = 'relative';
|
|
||||||
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';
|
|
||||||
content.style.maxWidth = '800px';
|
|
||||||
content.style.maxHeight = '600px';
|
|
||||||
} else if (audioTypes.includes(fileExt)) {
|
|
||||||
content.style.width = '500px';
|
|
||||||
content.style.height = '300px';
|
|
||||||
content.style.maxWidth = '90vw';
|
|
||||||
content.style.maxHeight = '400px';
|
|
||||||
} else if (videoTypes.includes(fileExt)) {
|
|
||||||
content.style.width = '90vw';
|
|
||||||
content.style.height = '80vh';
|
|
||||||
content.style.maxWidth = '1200px';
|
|
||||||
content.style.maxHeight = '800px';
|
|
||||||
} else if (isPDF) {
|
|
||||||
content.style.width = '90vw';
|
|
||||||
content.style.height = '90vh';
|
|
||||||
content.style.maxWidth = '1200px';
|
|
||||||
content.style.maxHeight = '900px';
|
|
||||||
frame.classList.add('pdf');
|
|
||||||
} else {
|
|
||||||
content.style.width = '80vw';
|
|
||||||
content.style.height = '80vh';
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.style.display = 'block';
|
|
||||||
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>
|
|
||||||
Reference in New Issue
Block a user