фиксы и фичи
This commit is contained in:
20
config.ini
Executable file
20
config.ini
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
[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
|
||||||
531
index.c
Executable file → Normal file
531
index.c
Executable file → Normal file
@@ -8,11 +8,146 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#define MAX_ENTRIES 1000
|
// === КОНФИГУРАЦИЯ ===
|
||||||
#define TEMPLATE_PATH "/home/romkazvo/www/cgi-bin/template.html"
|
|
||||||
#define OUTPUT_BUFFER_SIZE 65536
|
typedef struct {
|
||||||
#define PASSWD_FILE "/home/romkazvo/www/cgi-bin/.htpasswd"
|
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];
|
||||||
|
} config_t;
|
||||||
|
|
||||||
|
config_t g_config;
|
||||||
|
|
||||||
|
// Простой парсер INI (без зависимостей)
|
||||||
|
int load_config(const char *path, config_t *cfg) {
|
||||||
|
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(cfg->base_path, value, sizeof(cfg->base_path) - 1);
|
||||||
|
else if (strcmp(key, "template_path") == 0) strncpy(cfg->template_path, value, sizeof(cfg->template_path) - 1);
|
||||||
|
else if (strcmp(key, "passwd_file") == 0) strncpy(cfg->passwd_file, value, sizeof(cfg->passwd_file) - 1);
|
||||||
|
}
|
||||||
|
else if (strcmp(current_section, "limits") == 0) {
|
||||||
|
if (strcmp(key, "max_entries") == 0) cfg->max_entries = atoi(value);
|
||||||
|
}
|
||||||
|
else if (strcmp(current_section, "hidden") == 0) {
|
||||||
|
if (strcmp(key, "dirs") == 0) {
|
||||||
|
char *token = strtok(value, ",");
|
||||||
|
cfg->hidden_dirs_count = 0;
|
||||||
|
while (token && cfg->hidden_dirs_count < 10) {
|
||||||
|
while (*token == ' ') token++;
|
||||||
|
strncpy(cfg->hidden_dirs[cfg->hidden_dirs_count], token, 255);
|
||||||
|
cfg->hidden_dirs[cfg->hidden_dirs_count][255] = '\0';
|
||||||
|
cfg->hidden_dirs_count++;
|
||||||
|
token = strtok(NULL, ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(key, "files") == 0) {
|
||||||
|
char *token = strtok(value, ",");
|
||||||
|
cfg->hidden_files_count = 0;
|
||||||
|
while (token && cfg->hidden_files_count < 10) {
|
||||||
|
while (*token == ' ') token++;
|
||||||
|
strncpy(cfg->hidden_files[cfg->hidden_files_count], token, 255);
|
||||||
|
cfg->hidden_files[cfg->hidden_files_count][255] = '\0';
|
||||||
|
cfg->hidden_files_count++;
|
||||||
|
token = strtok(NULL, ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(current_section, "preview") == 0) {
|
||||||
|
if (strcmp(key, "image") == 0) strncpy(cfg->preview_image, value, sizeof(cfg->preview_image) - 1);
|
||||||
|
else if (strcmp(key, "text") == 0) strncpy(cfg->preview_text, value, sizeof(cfg->preview_text) - 1);
|
||||||
|
else if (strcmp(key, "audio") == 0) strncpy(cfg->preview_audio, value, sizeof(cfg->preview_audio) - 1);
|
||||||
|
else if (strcmp(key, "video") == 0) strncpy(cfg->preview_video, value, sizeof(cfg->preview_video) - 1);
|
||||||
|
else if (strcmp(key, "pdf") == 0) strncpy(cfg->preview_pdf, value, sizeof(cfg->preview_pdf) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_default_config(config_t *cfg) {
|
||||||
|
strcpy(cfg->base_path, "/home/romkazvo/www");
|
||||||
|
strcpy(cfg->template_path, "/home/romkazvo/www/cgi-bin/template.html");
|
||||||
|
strcpy(cfg->passwd_file, "/home/romkazvo/www/cgi-bin/.htpasswd");
|
||||||
|
cfg->max_entries = 1000;
|
||||||
|
cfg->hidden_dirs_count = 0;
|
||||||
|
cfg->hidden_files_count = 0;
|
||||||
|
strcpy(cfg->preview_image, "jpg,jpeg,png,gif,webp,bmp,svg,ico");
|
||||||
|
strcpy(cfg->preview_text, "txt,md,html,htm,css,js,json,xml,csv");
|
||||||
|
strcpy(cfg->preview_audio, "mp3,wav,ogg,flac,m4a,aac");
|
||||||
|
strcpy(cfg->preview_video, "mp4,webm,ogv,mov,avi,mkv");
|
||||||
|
strcpy(cfg->preview_pdf, "pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ОСТАЛЬНОЙ КОД (без изменений, только теперь используем g_config вместо #define) ===
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[256];
|
char name[256];
|
||||||
@@ -22,9 +157,11 @@ typedef struct {
|
|||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
char file_url[1024];
|
char file_url[1024];
|
||||||
int is_protected;
|
int is_protected;
|
||||||
|
time_t mtime;
|
||||||
|
int file_count;
|
||||||
|
long long dir_size;
|
||||||
} entry_t;
|
} entry_t;
|
||||||
|
|
||||||
// Быстрый буферизированный вывод
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
size_t size;
|
size_t size;
|
||||||
@@ -32,7 +169,7 @@ typedef struct {
|
|||||||
} buffer_t;
|
} buffer_t;
|
||||||
|
|
||||||
void buffer_init(buffer_t *buf) {
|
void buffer_init(buffer_t *buf) {
|
||||||
buf->capacity = OUTPUT_BUFFER_SIZE;
|
buf->capacity = 65536;
|
||||||
buf->data = malloc(buf->capacity);
|
buf->data = malloc(buf->capacity);
|
||||||
buf->size = 0;
|
buf->size = 0;
|
||||||
}
|
}
|
||||||
@@ -47,48 +184,54 @@ void buffer_append(buffer_t *buf, const char *str) {
|
|||||||
buf->size += len;
|
buf->size += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer_append_size(buffer_t *buf, const char *str, size_t len) {
|
|
||||||
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) {
|
void buffer_free(buffer_t *buf) {
|
||||||
free(buf->data);
|
free(buf->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция проверки пароля для папки
|
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 check_folder_password(const char *folder_name, const char *password) {
|
int check_folder_password(const char *folder_name, const char *password) {
|
||||||
FILE *f = fopen(PASSWD_FILE, "r");
|
if (!password || !folder_name || password[0] == '\0') return 0;
|
||||||
if (!f) return 1; // Если нет файла с паролями - доступ разрешен
|
|
||||||
|
FILE *f = fopen(g_config.passwd_file, "r");
|
||||||
|
if (!f) return 1;
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
while (fgets(line, sizeof(line), f)) {
|
while (fgets(line, sizeof(line), f)) {
|
||||||
// Убираем перевод строки
|
|
||||||
line[strcspn(line, "\r\n")] = 0;
|
line[strcspn(line, "\r\n")] = 0;
|
||||||
|
|
||||||
char *colon = strchr(line, ':');
|
char *colon = strchr(line, ':');
|
||||||
if (!colon) continue;
|
if (!colon) continue;
|
||||||
|
|
||||||
*colon = '\0';
|
*colon = '\0';
|
||||||
char *folder = line;
|
char *folder = line;
|
||||||
char *pass = colon + 1;
|
char *pass = colon + 1;
|
||||||
|
|
||||||
if (strcmp(folder, folder_name) == 0) {
|
if (strcmp(folder, folder_name) == 0) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return strcmp(pass, password) == 0;
|
return strcmp(pass, password) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return 1; // Папка не найдена в списке - доступ разрешен
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяет, защищена ли папка паролем
|
|
||||||
int is_folder_protected(const char *folder_name) {
|
int is_folder_protected(const char *folder_name) {
|
||||||
FILE *f = fopen(PASSWD_FILE, "r");
|
if (!folder_name || folder_name[0] == '\0') return 0;
|
||||||
|
|
||||||
|
FILE *f = fopen(g_config.passwd_file, "r");
|
||||||
if (!f) return 0;
|
if (!f) return 0;
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
@@ -96,7 +239,6 @@ int is_folder_protected(const char *folder_name) {
|
|||||||
line[strcspn(line, "\r\n")] = 0;
|
line[strcspn(line, "\r\n")] = 0;
|
||||||
char *colon = strchr(line, ':');
|
char *colon = strchr(line, ':');
|
||||||
if (!colon) continue;
|
if (!colon) continue;
|
||||||
|
|
||||||
*colon = '\0';
|
*colon = '\0';
|
||||||
if (strcmp(line, folder_name) == 0) {
|
if (strcmp(line, folder_name) == 0) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
@@ -107,121 +249,106 @@ int is_folder_protected(const char *folder_name) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
struct dirent *entry;
|
||||||
|
struct stat statbuf;
|
||||||
|
char fullpath[1024];
|
||||||
|
long long size = 0;
|
||||||
|
dir = opendir(path);
|
||||||
|
if (!dir) return 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* get_file_icon(const char* filename) {
|
||||||
const char *ext = strrchr(filename, '.');
|
const char *ext = strrchr(filename, '.');
|
||||||
if (!ext) return "📄";
|
if (!ext) return "📄";
|
||||||
|
|
||||||
ext++;
|
ext++;
|
||||||
|
if (is_string_in_list(ext, g_config.preview_image)) return "🖼️";
|
||||||
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
|
if (is_string_in_list(ext, g_config.preview_audio)) return "🎵";
|
||||||
strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 ||
|
if (is_string_in_list(ext, g_config.preview_video)) return "🎬";
|
||||||
strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0)
|
|
||||||
return "🖼️";
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 ||
|
|
||||||
strcasecmp(ext, "flac") == 0 || strcasecmp(ext, "ogg") == 0)
|
|
||||||
return "🎵";
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "avi") == 0 ||
|
|
||||||
strcasecmp(ext, "mkv") == 0 || strcasecmp(ext, "mov") == 0)
|
|
||||||
return "🎬";
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 ||
|
if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 ||
|
||||||
strcasecmp(ext, "7z") == 0 || strcasecmp(ext, "tar") == 0 ||
|
strcasecmp(ext, "7z") == 0 || strcasecmp(ext, "tar") == 0 ||
|
||||||
strcasecmp(ext, "gz") == 0)
|
strcasecmp(ext, "gz") == 0) return "📦";
|
||||||
return "📦";
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "pdf") == 0) return "📕";
|
if (strcasecmp(ext, "pdf") == 0) return "📕";
|
||||||
if (strcasecmp(ext, "doc") == 0 || strcasecmp(ext, "docx") == 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, "xls") == 0 || strcasecmp(ext, "xlsx") == 0) return "📗";
|
||||||
if (strcasecmp(ext, "txt") == 0) return "📝";
|
if (strcasecmp(ext, "txt") == 0) return "📝";
|
||||||
|
|
||||||
return "📄";
|
return "📄";
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_previewable(const char* filename) {
|
int is_previewable(const char* filename) {
|
||||||
const char *ext = strrchr(filename, '.');
|
const char *ext = strrchr(filename, '.');
|
||||||
if (!ext) return 0;
|
if (!ext) return 0;
|
||||||
|
|
||||||
ext++;
|
ext++;
|
||||||
|
return (is_string_in_list(ext, g_config.preview_image) ||
|
||||||
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
|
is_string_in_list(ext, g_config.preview_text) ||
|
||||||
strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 ||
|
is_string_in_list(ext, g_config.preview_audio) ||
|
||||||
strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0 ||
|
is_string_in_list(ext, g_config.preview_video) ||
|
||||||
strcasecmp(ext, "svg") == 0 || strcasecmp(ext, "ico") == 0)
|
is_string_in_list(ext, g_config.preview_pdf));
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 ||
|
|
||||||
strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0 ||
|
|
||||||
strcasecmp(ext, "css") == 0 || strcasecmp(ext, "js") == 0 ||
|
|
||||||
strcasecmp(ext, "json") == 0 || strcasecmp(ext, "xml") == 0 ||
|
|
||||||
strcasecmp(ext, "csv") == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "pdf") == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 ||
|
|
||||||
strcasecmp(ext, "ogg") == 0 || strcasecmp(ext, "flac") == 0 ||
|
|
||||||
strcasecmp(ext, "m4a") == 0 || strcasecmp(ext, "aac") == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "webm") == 0 ||
|
|
||||||
strcasecmp(ext, "ogv") == 0 || strcasecmp(ext, "mov") == 0 ||
|
|
||||||
strcasecmp(ext, "avi") == 0 || strcasecmp(ext, "mkv") == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int compare_entries(const void *a, const void *b) {
|
int compare_entries(const void *a, const void *b) {
|
||||||
const entry_t *entryA = (const entry_t *)a;
|
const entry_t *entryA = (const entry_t *)a;
|
||||||
const entry_t *entryB = (const entry_t *)b;
|
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;
|
||||||
if (!entryA->is_dir && entryB->is_dir) return 1;
|
if (!entryA->is_dir && entryB->is_dir) return 1;
|
||||||
|
|
||||||
return strcasecmp(entryA->name, entryB->name);
|
return strcasecmp(entryA->name, entryB->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void format_size(long size, char* buffer) {
|
void format_size(long long size, char* buffer) {
|
||||||
if (size < 1024) {
|
if (size < 1024) snprintf(buffer, 32, "%lld B", size);
|
||||||
snprintf(buffer, 32, "%ld B", size);
|
else if (size < 1024 * 1024) snprintf(buffer, 32, "%.1f KB", size / 1024.0);
|
||||||
} else if (size < 1024 * 1024) {
|
else if (size < 1024 * 1024 * 1024) snprintf(buffer, 32, "%.1f MB", size / (1024.0 * 1024.0));
|
||||||
snprintf(buffer, 32, "%.1f KB", size / 1024.0);
|
else snprintf(buffer, 32, "%.1f GB", size / (1024.0 * 1024.0 * 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) {
|
void url_encode(const char *src, char *dst, size_t dst_size) {
|
||||||
static const char *hex = "0123456789ABCDEF";
|
static const char *hex = "0123456789ABCDEF";
|
||||||
char *p = dst;
|
char *p = dst;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
while (*src && i < dst_size - 3) {
|
while (*src && i < dst_size - 3) {
|
||||||
unsigned char c = (unsigned char)*src;
|
unsigned char c = (unsigned char)*src;
|
||||||
|
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||||
if ((c >= 'A' && c <= 'Z') ||
|
(c >= '0' && c <= '9') || strchr("-_.~", c)) {
|
||||||
(c >= 'a' && c <= 'z') ||
|
*p++ = c; i++;
|
||||||
(c >= '0' && c <= '9') ||
|
} else if (c == ' ') {
|
||||||
strchr("-_.~", c)) {
|
*p++ = '%'; *p++ = '2'; *p++ = '0'; i += 3;
|
||||||
*p++ = c;
|
} else {
|
||||||
i++;
|
*p++ = '%'; *p++ = hex[(c >> 4) & 0xF]; *p++ = hex[c & 0xF]; i += 3;
|
||||||
}
|
|
||||||
else if (c == ' ') {
|
|
||||||
*p++ = '%';
|
|
||||||
*p++ = '2';
|
|
||||||
*p++ = '0';
|
|
||||||
i += 3;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*p++ = '%';
|
|
||||||
*p++ = hex[(c >> 4) & 0xF];
|
|
||||||
*p++ = hex[c & 0xF];
|
|
||||||
i += 3;
|
|
||||||
}
|
}
|
||||||
src++;
|
src++;
|
||||||
}
|
}
|
||||||
@@ -231,65 +358,41 @@ 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 url_decode_enhanced(const char *src, char *dst, size_t dst_size) {
|
||||||
char *p = dst;
|
char *p = dst;
|
||||||
size_t decoded_len = 0;
|
size_t decoded_len = 0;
|
||||||
|
|
||||||
while (*src && decoded_len < dst_size - 1) {
|
while (*src && decoded_len < dst_size - 1) {
|
||||||
if (*src == '%') {
|
if (*src == '%') {
|
||||||
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
|
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
|
||||||
char hex[3] = {src[1], src[2], '\0'};
|
char hex[3] = {src[1], src[2], '\0'};
|
||||||
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
|
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
|
||||||
|
if (c >= 0x80) { *p++ = c; decoded_len++; }
|
||||||
if (c >= 0x80) {
|
else if (c >= 0x20 || c == 0x0A || c == 0x0D) { *p++ = c; decoded_len++; }
|
||||||
*p++ = c;
|
else { *p++ = '_'; decoded_len++; }
|
||||||
decoded_len++;
|
|
||||||
} else if (c >= 0x20 || c == 0x0A || c == 0x0D) {
|
|
||||||
*p++ = c;
|
|
||||||
decoded_len++;
|
|
||||||
} else {
|
|
||||||
*p++ = '_';
|
|
||||||
decoded_len++;
|
|
||||||
}
|
|
||||||
src += 3;
|
src += 3;
|
||||||
} else {
|
} else { *p++ = *src++; decoded_len++; }
|
||||||
*p++ = *src++;
|
|
||||||
decoded_len++;
|
|
||||||
}
|
|
||||||
} else if (*src == '+') {
|
} else if (*src == '+') {
|
||||||
*p++ = ' ';
|
*p++ = ' '; decoded_len++; src++;
|
||||||
decoded_len++;
|
|
||||||
src++;
|
|
||||||
} else {
|
} else {
|
||||||
*p++ = *src++;
|
*p++ = *src++; decoded_len++;
|
||||||
decoded_len++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
void safe_path_join(char *result, size_t result_size, const char *base, const char *path) {
|
|
||||||
snprintf(result, result_size, "%s/%s", base, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
||||||
buffer_append(buf, "<div class=\"breadcrumb\">\n");
|
buffer_append(buf, "<div class=\"breadcrumb\">\n");
|
||||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\">🏠 Главная</a>");
|
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\">🏠 Главная</a>");
|
||||||
|
|
||||||
if (display_path && display_path[0]) {
|
if (display_path && display_path[0]) {
|
||||||
char temp_path[1024] = "";
|
char temp_path[1024] = "";
|
||||||
char encoded[2048];
|
char encoded[2048];
|
||||||
char *path_copy = strdup(display_path);
|
char *path_copy = strdup(display_path);
|
||||||
char *token = strtok(path_copy, "/");
|
char *token = strtok(path_copy, "/");
|
||||||
|
|
||||||
while (token) {
|
while (token) {
|
||||||
buffer_append(buf, " / ");
|
buffer_append(buf, " / ");
|
||||||
|
|
||||||
if (temp_path[0]) strcat(temp_path, "/");
|
if (temp_path[0]) strcat(temp_path, "/");
|
||||||
strcat(temp_path, token);
|
strcat(temp_path, token);
|
||||||
|
|
||||||
url_encode(temp_path, encoded, sizeof(encoded));
|
url_encode(temp_path, encoded, sizeof(encoded));
|
||||||
char link[4096];
|
char link[4096];
|
||||||
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, token);
|
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, token);
|
||||||
buffer_append(buf, link);
|
buffer_append(buf, link);
|
||||||
|
|
||||||
token = strtok(NULL, "/");
|
token = strtok(NULL, "/");
|
||||||
}
|
}
|
||||||
free(path_copy);
|
free(path_copy);
|
||||||
@@ -315,13 +418,10 @@ void print_password_form(buffer_t *buf, const char *folder_name) {
|
|||||||
buffer_append(buf, " <button type=\"submit\">Войти</button>\n");
|
buffer_append(buf, " <button type=\"submit\">Войти</button>\n");
|
||||||
buffer_append(buf, " </form>\n");
|
buffer_append(buf, " </form>\n");
|
||||||
buffer_append(buf, " <p class=\"error-msg\">");
|
buffer_append(buf, " <p class=\"error-msg\">");
|
||||||
|
|
||||||
// Проверяем, была ли ошибка ввода пароля
|
|
||||||
char *query_string = getenv("QUERY_STRING");
|
char *query_string = getenv("QUERY_STRING");
|
||||||
if (query_string && strstr(query_string, "error=1")) {
|
if (query_string && strstr(query_string, "error=1")) {
|
||||||
buffer_append(buf, "❌ Неверный пароль! Попробуйте снова.");
|
buffer_append(buf, "❌ Неверный пароль! Попробуйте снова.");
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_append(buf, "</p>\n");
|
buffer_append(buf, "</p>\n");
|
||||||
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\" class=\"back-link\">← Вернуться на главную</a>\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");
|
||||||
@@ -334,22 +434,22 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
struct stat file_stat;
|
struct stat file_stat;
|
||||||
char full_path[1024];
|
char full_path[1024];
|
||||||
|
|
||||||
entry_t entries[MAX_ENTRIES];
|
entry_t *entries = malloc(sizeof(entry_t) * g_config.max_entries);
|
||||||
|
if (!entries) {
|
||||||
|
buffer_append(buf, "<div class=\"error\">Ошибка выделения памяти</div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int entry_count = 0;
|
int entry_count = 0;
|
||||||
int dir_count = 0, file_count = 0;
|
int dir_count = 0, file_count = 0;
|
||||||
long long total_size = 0;
|
long long total_size = 0;
|
||||||
|
|
||||||
// Проверяем, не защищена ли текущая папка паролем
|
|
||||||
char folder_name[256] = "";
|
char folder_name[256] = "";
|
||||||
if (display_path && display_path[0]) {
|
if (display_path && display_path[0]) {
|
||||||
char *last_slash = strrchr(display_path, '/');
|
char *last_slash = strrchr(display_path, '/');
|
||||||
if (last_slash) {
|
if (last_slash) strcpy(folder_name, last_slash + 1);
|
||||||
strcpy(folder_name, last_slash + 1);
|
else strcpy(folder_name, display_path);
|
||||||
} else {
|
|
||||||
strcpy(folder_name, display_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем пароль
|
|
||||||
char *query_string = getenv("QUERY_STRING");
|
char *query_string = getenv("QUERY_STRING");
|
||||||
char *password = NULL;
|
char *password = NULL;
|
||||||
if (query_string) {
|
if (query_string) {
|
||||||
@@ -362,7 +462,6 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
char pass_buf[256];
|
char pass_buf[256];
|
||||||
strncpy(pass_buf, pass_start, pass_len);
|
strncpy(pass_buf, pass_start, pass_len);
|
||||||
pass_buf[pass_len] = '\0';
|
pass_buf[pass_len] = '\0';
|
||||||
// URL декодируем пароль
|
|
||||||
char decoded_pass[256];
|
char decoded_pass[256];
|
||||||
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
|
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
|
||||||
password = decoded_pass;
|
password = decoded_pass;
|
||||||
@@ -372,8 +471,8 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
|
|
||||||
if (is_folder_protected(folder_name)) {
|
if (is_folder_protected(folder_name)) {
|
||||||
if (!password || !check_folder_password(folder_name, password)) {
|
if (!password || !check_folder_password(folder_name, password)) {
|
||||||
// Показываем форму ввода пароля
|
|
||||||
print_password_form(buf, folder_name);
|
print_password_form(buf, folder_name);
|
||||||
|
free(entries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,14 +481,12 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
dir = opendir(base_path);
|
dir = opendir(base_path);
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
char alt_path[1024];
|
char alt_path[1024];
|
||||||
snprintf(alt_path, sizeof(alt_path), "/home/romkazvo/www");
|
snprintf(alt_path, sizeof(alt_path), "%s", g_config.base_path);
|
||||||
if (display_path[0]) {
|
if (display_path[0]) {
|
||||||
char *encoded = strdup(display_path);
|
char *encoded = strdup(display_path);
|
||||||
url_decode_enhanced(encoded, alt_path + strlen(alt_path),
|
url_decode_enhanced(encoded, alt_path + strlen(alt_path), sizeof(alt_path) - strlen(alt_path));
|
||||||
sizeof(alt_path) - strlen(alt_path));
|
|
||||||
free(encoded);
|
free(encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
dir = opendir(alt_path);
|
dir = opendir(alt_path);
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
buffer_append(buf, "<div class=\"empty-state\">\n");
|
||||||
@@ -402,27 +499,25 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, error_msg);
|
buffer_append(buf, error_msg);
|
||||||
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
|
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
|
||||||
buffer_append(buf, "</div>\n");
|
buffer_append(buf, "</div>\n");
|
||||||
|
free(entries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Предварительная обработка записей
|
while ((entry = readdir(dir)) != NULL && entry_count < g_config.max_entries) {
|
||||||
while ((entry = readdir(dir)) != NULL && entry_count < MAX_ENTRIES) {
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
|
||||||
continue;
|
int hidden = 0;
|
||||||
if (strcmp(entry->d_name, "cgi-bin") == 0)
|
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
||||||
continue;
|
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
||||||
// Скрываем файл с паролями (на всякий случай, если кто-то сможет получить доступ)
|
}
|
||||||
if (strcmp(entry->d_name, ".htpasswd") == 0)
|
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
||||||
continue;
|
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
||||||
if (strcmp(entry->d_name, ".htaccess") == 0)
|
}
|
||||||
continue;
|
if (entry->d_name[0] == '.') hidden = 1;
|
||||||
// Скрываем все файлы, начинающиеся с точки
|
if (hidden) continue;
|
||||||
if (entry->d_name[0] == '.')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
||||||
|
|
||||||
if (stat(full_path, &file_stat) == 0) {
|
if (stat(full_path, &file_stat) == 0) {
|
||||||
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
|
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
|
||||||
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
|
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
|
||||||
@@ -430,8 +525,11 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
if (S_ISDIR(file_stat.st_mode)) {
|
if (S_ISDIR(file_stat.st_mode)) {
|
||||||
entries[entry_count].is_dir = 1;
|
entries[entry_count].is_dir = 1;
|
||||||
entries[entry_count].size = 0;
|
entries[entry_count].size = 0;
|
||||||
|
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)) {
|
if (is_folder_protected(entry->d_name)) {
|
||||||
strcpy(entries[entry_count].icon, "🔒");
|
strcpy(entries[entry_count].icon, "🔒");
|
||||||
entries[entry_count].is_protected = 1;
|
entries[entry_count].is_protected = 1;
|
||||||
@@ -441,22 +539,20 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
}
|
}
|
||||||
|
|
||||||
char new_path[2048];
|
char new_path[2048];
|
||||||
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path,
|
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
||||||
display_path[0] ? "/" : "", entries[entry_count].name);
|
|
||||||
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
|
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
|
||||||
|
|
||||||
dir_count++;
|
dir_count++;
|
||||||
} else if (S_ISREG(file_stat.st_mode)) {
|
} else if (S_ISREG(file_stat.st_mode)) {
|
||||||
entries[entry_count].is_dir = 0;
|
entries[entry_count].is_dir = 0;
|
||||||
entries[entry_count].size = file_stat.st_size;
|
entries[entry_count].size = file_stat.st_size;
|
||||||
|
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));
|
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
|
||||||
entries[entry_count].is_protected = 0;
|
entries[entry_count].is_protected = 0;
|
||||||
|
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
||||||
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url),
|
|
||||||
"%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
|
||||||
|
|
||||||
file_count++;
|
|
||||||
total_size += file_stat.st_size;
|
total_size += file_stat.st_size;
|
||||||
|
file_count++;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -471,14 +567,13 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
|
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
|
||||||
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
|
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
|
||||||
buffer_append(buf, "</div>\n");
|
buffer_append(buf, "</div>\n");
|
||||||
|
free(entries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qsort(entries, entry_count, sizeof(entry_t), compare_entries);
|
qsort(entries, entry_count, sizeof(entry_t), compare_entries);
|
||||||
|
|
||||||
buffer_append(buf, "<ul>\n");
|
buffer_append(buf, "<ul>\n");
|
||||||
|
|
||||||
// Вывод папок
|
|
||||||
if (dir_count > 0) {
|
if (dir_count > 0) {
|
||||||
char section_title[128];
|
char section_title[128];
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count);
|
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count);
|
||||||
@@ -487,9 +582,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
for (int i = 0; i < entry_count; i++) {
|
for (int i = 0; i < entry_count; i++) {
|
||||||
if (entries[i].is_dir) {
|
if (entries[i].is_dir) {
|
||||||
buffer_append(buf, " <li>\n");
|
buffer_append(buf, " <li>\n");
|
||||||
|
|
||||||
if (entries[i].is_protected) {
|
if (entries[i].is_protected) {
|
||||||
// Защищенная папка
|
|
||||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||||
buffer_append(buf, " <a class=\"dir-link protected-folder\" href=\"javascript:void(0)\" onclick=\"showPasswordForm('");
|
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, entries[i].encoded_path);
|
||||||
@@ -510,7 +603,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, " </div>\n");
|
buffer_append(buf, " </div>\n");
|
||||||
buffer_append(buf, " </div>\n");
|
buffer_append(buf, " </div>\n");
|
||||||
} else {
|
} else {
|
||||||
// Обычная папка
|
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||||
char dir_link[512];
|
char dir_link[512];
|
||||||
snprintf(dir_link, sizeof(dir_link),
|
snprintf(dir_link, sizeof(dir_link),
|
||||||
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n",
|
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n",
|
||||||
@@ -521,15 +614,25 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " <span class=\"file-name\">");
|
buffer_append(buf, " <span class=\"file-name\">");
|
||||||
buffer_append(buf, entries[i].name);
|
buffer_append(buf, entries[i].name);
|
||||||
|
if (entries[i].file_count > 0) {
|
||||||
|
char count_str[32];
|
||||||
|
snprintf(count_str, sizeof(count_str), " (%d)", entries[i].file_count);
|
||||||
|
buffer_append(buf, count_str);
|
||||||
|
}
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " </a>\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);
|
||||||
|
buffer_append(buf, "')\" title=\"Копировать ссылку на папку\">🔗</button>\n");
|
||||||
|
buffer_append(buf, " </div>\n");
|
||||||
|
buffer_append(buf, " </div>\n");
|
||||||
}
|
}
|
||||||
buffer_append(buf, " </li>\n");
|
buffer_append(buf, " </li>\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вывод файлов
|
|
||||||
if (file_count > 0) {
|
if (file_count > 0) {
|
||||||
char section_title[128];
|
char section_title[128];
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
|
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
|
||||||
@@ -538,11 +641,12 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
for (int i = 0; i < entry_count; i++) {
|
for (int i = 0; i < entry_count; i++) {
|
||||||
if (!entries[i].is_dir) {
|
if (!entries[i].is_dir) {
|
||||||
char size_str[32];
|
char size_str[32];
|
||||||
|
char date_str[64];
|
||||||
format_size(entries[i].size, size_str);
|
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, " <li>\n");
|
||||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||||
|
|
||||||
buffer_append(buf, " <a class=\"file-link\" href=\"/");
|
buffer_append(buf, " <a class=\"file-link\" href=\"/");
|
||||||
buffer_append(buf, entries[i].file_url);
|
buffer_append(buf, entries[i].file_url);
|
||||||
buffer_append(buf, "\" download>\n");
|
buffer_append(buf, "\" download>\n");
|
||||||
@@ -553,16 +657,19 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, entries[i].name);
|
buffer_append(buf, entries[i].name);
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " </a>\n");
|
buffer_append(buf, " </a>\n");
|
||||||
|
|
||||||
buffer_append(buf, " <div class=\"file-controls\">\n");
|
buffer_append(buf, " <div class=\"file-controls\">\n");
|
||||||
|
|
||||||
if (is_previewable(entries[i].name)) {
|
if (is_previewable(entries[i].name)) {
|
||||||
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('/");
|
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('/");
|
||||||
buffer_append(buf, entries[i].file_url);
|
buffer_append(buf, entries[i].file_url);
|
||||||
buffer_append(buf, "')\" title=\"Открыть в браузере\">👁</button>\n");
|
buffer_append(buf, "')\" title=\"Открыть в браузере\">👁</button>\n");
|
||||||
}
|
}
|
||||||
|
buffer_append(buf, " <button class=\"copy-btn\" onclick=\"copyLink('/");
|
||||||
buffer_append(buf, " <span class=\"size\">");
|
buffer_append(buf, entries[i].file_url);
|
||||||
|
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, size_str);
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " </div>\n");
|
buffer_append(buf, " </div>\n");
|
||||||
@@ -574,7 +681,6 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
|
|
||||||
buffer_append(buf, "</ul>\n");
|
buffer_append(buf, "</ul>\n");
|
||||||
|
|
||||||
// Статистика
|
|
||||||
char total_size_str[32];
|
char total_size_str[32];
|
||||||
format_size(total_size, total_size_str);
|
format_size(total_size, total_size_str);
|
||||||
char stats[256];
|
char stats[256];
|
||||||
@@ -582,10 +688,12 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
|
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
|
||||||
dir_count, file_count, total_size_str);
|
dir_count, file_count, total_size_str);
|
||||||
buffer_append(buf, stats);
|
buffer_append(buf, stats);
|
||||||
|
|
||||||
|
free(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_template(const char *base_path, const char *display_path) {
|
void print_template(const char *base_path, const char *display_path) {
|
||||||
FILE *file = fopen(TEMPLATE_PATH, "r");
|
FILE *file = fopen(g_config.template_path, "r");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
printf("Error: Cannot read template\n");
|
printf("Error: Cannot read template\n");
|
||||||
return;
|
return;
|
||||||
@@ -614,9 +722,16 @@ int main() {
|
|||||||
setlocale(LC_ALL, "en_US.UTF-8");
|
setlocale(LC_ALL, "en_US.UTF-8");
|
||||||
setlocale(LC_CTYPE, "en_US.UTF-8");
|
setlocale(LC_CTYPE, "en_US.UTF-8");
|
||||||
|
|
||||||
|
// Загружаем конфиг
|
||||||
|
if (load_config("/home/romkazvo/www/cgi-bin/config.ini", &g_config) != 0) {
|
||||||
|
// Если конфиг не найден — используем дефолты
|
||||||
|
set_default_config(&g_config);
|
||||||
|
}
|
||||||
|
|
||||||
printf("Content-type: text/html; charset=utf-8\n\n");
|
printf("Content-type: text/html; charset=utf-8\n\n");
|
||||||
|
|
||||||
char base_path[1024] = "/home/romkazvo/www";
|
char base_path[1024];
|
||||||
|
strcpy(base_path, g_config.base_path);
|
||||||
char display_path[1024] = "";
|
char display_path[1024] = "";
|
||||||
char safe_display_path[1024] = "";
|
char safe_display_path[1024] = "";
|
||||||
|
|
||||||
@@ -627,35 +742,30 @@ int main() {
|
|||||||
path_start += 5;
|
path_start += 5;
|
||||||
char *path_end = strchr(path_start, '&');
|
char *path_end = strchr(path_start, '&');
|
||||||
int path_len = path_end ? path_end - path_start : strlen(path_start);
|
int path_len = path_end ? path_end - path_start : strlen(path_start);
|
||||||
|
|
||||||
if (path_len > 0 && path_len < sizeof(display_path) - 1) {
|
if (path_len > 0 && path_len < sizeof(display_path) - 1) {
|
||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
strncpy(encoded_path, path_start, path_len);
|
strncpy(encoded_path, path_start, path_len);
|
||||||
encoded_path[path_len] = '\0';
|
encoded_path[path_len] = '\0';
|
||||||
|
|
||||||
url_decode_enhanced(encoded_path, display_path, sizeof(display_path));
|
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);
|
strncpy(safe_display_path, display_path, sizeof(safe_display_path) - 1);
|
||||||
safe_display_path[sizeof(safe_display_path) - 1] = '\0';
|
safe_display_path[sizeof(safe_display_path) - 1] = '\0';
|
||||||
|
safe_path_join(base_path, sizeof(base_path), g_config.base_path, safe_display_path);
|
||||||
safe_path_join(base_path, sizeof(base_path), "/home/romkazvo/www", safe_display_path);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка неправильного пароля
|
|
||||||
if (strstr(query_string, "password=")) {
|
if (strstr(query_string, "password=")) {
|
||||||
char *error = strstr(query_string, "error=1");
|
char *error = strstr(query_string, "error=1");
|
||||||
if (!error) {
|
if (!error) {
|
||||||
// Проверяем пароль для текущей папки
|
|
||||||
char folder_name[256] = "";
|
char folder_name[256] = "";
|
||||||
if (display_path[0]) {
|
if (display_path[0]) {
|
||||||
char *last_slash = strrchr(display_path, '/');
|
char *last_slash = strrchr(display_path, '/');
|
||||||
if (last_slash) {
|
if (last_slash) strcpy(folder_name, last_slash + 1);
|
||||||
strcpy(folder_name, last_slash + 1);
|
else strcpy(folder_name, display_path);
|
||||||
} else {
|
|
||||||
strcpy(folder_name, display_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_folder_protected(folder_name)) {
|
if (is_folder_protected(folder_name)) {
|
||||||
char *pass_start = strstr(query_string, "password=");
|
char *pass_start = strstr(query_string, "password=");
|
||||||
if (pass_start) {
|
if (pass_start) {
|
||||||
@@ -668,9 +778,19 @@ int main() {
|
|||||||
pass_buf[pass_len] = '\0';
|
pass_buf[pass_len] = '\0';
|
||||||
char decoded_pass[256];
|
char decoded_pass[256];
|
||||||
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
|
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
|
||||||
|
if (decoded_pass[0] == '\0') {
|
||||||
|
char encoded_path[1024];
|
||||||
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!check_folder_password(folder_name, decoded_pass)) {
|
if (!check_folder_password(folder_name, decoded_pass)) {
|
||||||
// Неверный пароль - редирект с error=1
|
char encoded_path[1024];
|
||||||
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
||||||
@@ -682,7 +802,6 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
print_template(base_path, display_path);
|
print_template(base_path, display_path);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
550
style.css
550
style.css
@@ -174,11 +174,11 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 1rem 0.5rem;
|
padding: 0.75rem 0.5rem;
|
||||||
border-bottom: 1px solid var(--border-light);
|
border-bottom: 1px solid var(--border-light);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap; /* Исправлено: теперь не переносятся */
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child { border-bottom: none; }
|
li:last-child { border-bottom: none; }
|
||||||
@@ -194,7 +194,8 @@ li:hover {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 1rem;
|
gap: 0.75rem;
|
||||||
|
flex-wrap: nowrap; /* Запрещаем перенос */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-link {
|
.file-link {
|
||||||
@@ -219,21 +220,29 @@ li:hover {
|
|||||||
.file-controls {
|
.file-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.size {
|
.file-meta {
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 70px;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-meta.date {
|
||||||
|
min-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-meta.size {
|
||||||
|
min-width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon {
|
.file-icon {
|
||||||
margin-right: 1rem;
|
margin-right: 0.75rem;
|
||||||
font-size: 1.4rem;
|
font-size: 1.3rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -315,8 +324,8 @@ li:hover {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для кнопки предпросмотра */
|
/* Стили для кнопок */
|
||||||
.preview-btn {
|
.preview-btn, .copy-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
@@ -325,14 +334,15 @@ li:hover {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-btn:hover {
|
.preview-btn:hover, .copy-btn:hover {
|
||||||
background: var(--accent-blue);
|
background: var(--accent-blue);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--accent-blue);
|
border-color: var(--accent-blue);
|
||||||
@@ -340,249 +350,6 @@ li:hover {
|
|||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Модальное окно предпросмотра */
|
|
||||||
.preview-modal {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 2000;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0,0,0,0.8);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-content {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
margin: 2% auto;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
background: var(--bg-primary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-preview {
|
|
||||||
background: #dc3545;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-preview:hover {
|
|
||||||
background: #c82333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-iframe {
|
|
||||||
flex-grow: 1;
|
|
||||||
border: none;
|
|
||||||
background: white;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-iframe.pdf {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для контейнера изображения */
|
|
||||||
.preview-image-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 10px;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-image {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Улучшаем скроллинг для очень больших изображений */
|
|
||||||
.preview-modal::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-modal::-webkit-scrollbar-track {
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-modal::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255,255,255,0.3);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-modal::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.preview-content {
|
|
||||||
margin: 2.5% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-header {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-title {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-image-container {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.theme-toggle {
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
.scroll-top {
|
|
||||||
bottom: 15px;
|
|
||||||
right: 15px;
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
padding: 1rem 0.5rem;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
.breadcrumb {
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
padding: 0.75rem 0.25rem;
|
|
||||||
}
|
|
||||||
.file-row {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.file-controls {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.size {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
min-width: 60px;
|
|
||||||
}
|
|
||||||
.file-icon {
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
.file-name {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.section-title {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin: 1rem 0 0.25rem 0;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.preview-btn {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
.preview-content {
|
|
||||||
margin: 1% auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.preview-image-container {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
.preview-image {
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 360px) {
|
|
||||||
.container {
|
|
||||||
min-width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
.file-name {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.size {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
.file-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
.file-controls {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
.file-link {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для формы ввода пароля */
|
/* Стили для формы ввода пароля */
|
||||||
.password-form-container {
|
.password-form-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -690,26 +457,269 @@ li:hover {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.protected-folder .lock-icon {
|
/* Модальное окно предпросмотра */
|
||||||
font-size: 0.8rem;
|
.preview-modal {
|
||||||
margin-left: 0.5rem;
|
display: none;
|
||||||
opacity: 0.7;
|
position: fixed;
|
||||||
|
z-index: 2000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
margin: 2% auto;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-preview {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-preview:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-iframe {
|
||||||
|
flex-grow: 1;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-iframe.pdf {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-modal::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-modal::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уведомление */
|
||||||
|
#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: 768px) {
|
||||||
|
.file-meta {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.file-meta.date {
|
||||||
|
min-width: 75px;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
.preview-content {
|
||||||
|
margin: 2.5% auto;
|
||||||
|
}
|
||||||
|
.preview-header {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
.preview-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.preview-image-container {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Адаптивность для формы пароля */
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.password-form {
|
/* Скрываем дату на телефонах */
|
||||||
padding: 1.5rem;
|
.file-meta.date {
|
||||||
margin: 0 0.5rem;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
/* Уменьшаем размер кнопок */
|
||||||
.password-form h2 {
|
.preview-btn, .copy-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.file-name {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.file-icon {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
width: 20px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
.file-controls {
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
|
}
|
||||||
|
.file-row {
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.theme-toggle {
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
.scroll-top {
|
||||||
|
bottom: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
.breadcrumb {
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.4rem;
|
||||||
|
margin: 0.75rem 0 0.25rem 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.preview-content {
|
||||||
|
margin: 1% auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.preview-image-container {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.preview-image {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
#copyNotification {
|
||||||
|
bottom: 60px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-form input[type="password"],
|
@media (max-width: 360px) {
|
||||||
.password-form button {
|
.container {
|
||||||
font-size: 0.95rem;
|
min-width: 100%;
|
||||||
padding: 0.6rem;
|
margin: 0;
|
||||||
|
}
|
||||||
|
.file-name {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
.preview-btn, .copy-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
<meta name="keywords" content="RomkaZVO, mashup, mashup su, файлы, обмен, скачать, загрузить" />
|
<meta name="keywords" content="RomkaZVO, mashup, mashup su, файлы, обмен, скачать, загрузить" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Отключаем все alert на странице (на случай, если где-то закрались)
|
|
||||||
window.alert = function() {
|
|
||||||
console.log('Alert был заблокирован');
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
const value = `; ${document.cookie}`;
|
const value = `; ${document.cookie}`;
|
||||||
const parts = value.split(`; ${name}=`);
|
const parts = value.split(`; ${name}=`);
|
||||||
@@ -41,6 +36,54 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
/* Стили для кнопки копирования */
|
||||||
|
.copy-btn {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: var(--accent-blue);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
/* Уведомление */
|
||||||
|
#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) {
|
||||||
|
.copy-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
|
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
|
||||||
@@ -170,6 +213,51 @@
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== НОВАЯ ФУНКЦИЯ КОПИРОВАНИЯ ==========
|
||||||
|
window.copyLink = function(path) {
|
||||||
|
const url = window.location.origin + path;
|
||||||
|
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.scrollToTop = function() {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user