Files
Trashbox/index.c
2026-07-24 02:02:37 +08:00

1062 lines
47 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// /home/romkazvo/www/cgi-bin/index.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <locale.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 format_size(long long size, char* buffer);
void format_date(time_t mtime, char* buffer, size_t size);
void html_escape(const char *src, char *dst, size_t dst_size);
// === КОНФИГ ===
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];
} config_t;
config_t g_config;
// === ПАРАМЕТРЫ ===
typedef struct {
char sort_by[16];
int sort_order;
char search[256];
int recursive;
} query_params_t;
query_params_t g_params;
// === УТИЛИТЫ ===
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, "&lt;"); p += 4; i += 4; break;
case '>': strcpy(p, "&gt;"); p += 4; i += 4; break;
case '"': strcpy(p, "&quot;"); p += 6; i += 6; break;
case '&': strcpy(p, "&amp;"); p += 5; i += 5; break;
default: *p++ = *src; i++; break;
}
src++;
}
*p = '\0';
}
// === ПАРСЕР 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;
}
// === ОСНОВНЫЕ СТРУКТУРЫ ===
typedef struct {
char name[256];
int is_dir;
long size;
char icon[8];
char encoded_path[1024];
char file_url[1024];
int is_protected;
time_t mtime;
int file_count;
long long dir_size;
} entry_t;
typedef struct {
char *data;
size_t size;
size_t capacity;
} buffer_t;
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); }
// === БЕЗОПАСНОСТЬ ===
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) {
if (!password || !folder_name || password[0] == '\0') return 0;
FILE *f = fopen(g_config.passwd_file, "r");
if (!f) return 1;
char line[512];
while (fgets(line, sizeof(line), f)) {
line[strcspn(line, "\r\n")] = 0;
char *colon = strchr(line, ':');
if (!colon) continue;
*colon = '\0';
char *folder = line;
char *pass = colon + 1;
const char *last_slash = strrchr(folder_name, '/');
const char *base_name = last_slash ? last_slash + 1 : folder_name;
if (strcmp(folder, folder_name) == 0 || strcmp(folder, base_name) == 0) {
fclose(f);
return strcmp(pass, password) == 0;
}
}
fclose(f);
return 1;
}
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;
}
// === ФС ===
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];
strncpy(result->name, entry->d_name, sizeof(result->name) - 1);
result->name[sizeof(result->name) - 1] = '\0';
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));
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
(*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));
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
(*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;
}
// === ФОРМАТИРОВАНИЕ ===
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';
}
// === ОТРИСОВКА ===
void print_breadcrumb(buffer_t *buf, const char *display_path) {
buffer_append(buf, "<div class=\"breadcrumb\">\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\">🏠 Главная</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));
char link[4096];
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, safe_token);
buffer_append(buf, link);
token = strtok(NULL, "/");
}
free(path_copy);
}
buffer_append(buf, "\n</div>\n");
}
void print_password_form(buffer_t *buf, const char *folder_name) {
buffer_append(buf, "<div class=\"password-form-container\">\n");
buffer_append(buf, " <div class=\"password-form\">\n");
buffer_append(buf, " <div class=\"lock-icon\">🔒</div>\n");
buffer_append(buf, " <h2>Папка защищена паролем</h2>\n");
buffer_append(buf, " <p>Введите пароль для доступа к папке <strong>");
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=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
char encoded_path[1024];
url_encode(folder_name, encoded_path, sizeof(encoded_path));
buffer_append(buf, encoded_path);
buffer_append(buf, "\">\n");
buffer_append(buf, " <input type=\"password\" name=\"password\" placeholder=\"Введите пароль\" required>\n");
buffer_append(buf, " <button type=\"submit\">Войти</button>\n");
buffer_append(buf, " </form>\n");
buffer_append(buf, " <p class=\"error-msg\">");
char *query_string = getenv("QUERY_STRING");
if (query_string && strstr(query_string, "error=1")) {
buffer_append(buf, "❌ Неверный пароль! Попробуйте снова.");
}
buffer_append(buf, "</p>\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi\" class=\"back-link\">← Вернуться на главную</a>\n");
buffer_append(buf, " </div>\n");
buffer_append(buf, "</div>\n");
}
void print_content(buffer_t *buf, const char *base_path, const char *display_path) {
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char full_path[1024];
entry_t *entries = malloc(sizeof(entry_t) * g_config.max_entries);
if (!entries) {
buffer_append(buf, "<div class=\"error\">Ошибка выделения памяти</div>");
return;
}
int entry_count = 0, dir_count = 0, file_count = 0;
long long total_size = 0;
int is_recursive_search = 0;
char folder_name[256] = "";
if (display_path && display_path[0]) {
char *last_slash = strrchr(display_path, '/');
if (last_slash) strcpy(folder_name, last_slash + 1);
else strcpy(folder_name, display_path);
char *query_string = getenv("QUERY_STRING");
char *password = NULL;
if (query_string) {
char *pass_start = strstr(query_string, "password=");
if (pass_start) {
pass_start += 9;
char *pass_end = strchr(pass_start, '&');
int pass_len = pass_end ? pass_end - pass_start : strlen(pass_start);
if (pass_len > 0 && pass_len < 256) {
char pass_buf[256];
strncpy(pass_buf, pass_start, pass_len);
pass_buf[pass_len] = '\0';
char decoded_pass[256];
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
password = decoded_pass;
}
}
}
if (is_folder_protected(folder_name)) {
if (!password || !check_folder_password(folder_name, password)) {
print_password_form(buf, folder_name);
free(entries);
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");
buffer_append(buf, " <h3>Ошибка открытия директории</h3>\n");
char error_msg[512];
snprintf(error_msg, sizeof(error_msg), " <p>Путь: %s</p>\n", base_path);
buffer_append(buf, error_msg);
snprintf(error_msg, sizeof(error_msg), " <p>Ошибка: %s</p>\n", strerror(errno));
buffer_append(buf, error_msg);
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) {
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
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));
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", encoded_filename);
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));
buffer_append(buf, "<div class=\"toolbar\">\n");
buffer_append(buf, " <div class=\"search-box\">\n");
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
buffer_append(buf, encoded_path);
buffer_append(buf, "\">\n");
buffer_append(buf, " <input type=\"text\" name=\"search\" placeholder=\"🔍 Поиск по всему сайту...\" value=\"");
buffer_append(buf, safe_search);
buffer_append(buf, "\">\n");
buffer_append(buf, " <button type=\"submit\">Найти</button>\n");
buffer_append(buf, " </form>\n");
buffer_append(buf, " </div>\n");
buffer_append(buf, " <div class=\"sort-buttons\">\n");
buffer_append(buf, " <span class=\"sort-label\">Сортировка:</span>\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
buffer_append(buf, encoded_path);
buffer_append(buf, "&sort=name\" class=\"sort-btn");
if (strcmp(g_params.sort_by, "name") == 0) buffer_append(buf, " active");
buffer_append(buf, "\">📝 Имя</a>\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
buffer_append(buf, encoded_path);
buffer_append(buf, "&sort=size\" class=\"sort-btn");
if (strcmp(g_params.sort_by, "size") == 0) buffer_append(buf, " active");
buffer_append(buf, "\">📊 Размер</a>\n");
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
buffer_append(buf, encoded_path);
buffer_append(buf, "&sort=date\" class=\"sort-btn");
if (strcmp(g_params.sort_by, "date") == 0) buffer_append(buf, " active");
buffer_append(buf, "\">📅 Дата</a>\n");
buffer_append(buf, " </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");
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.7rem;color:var(--text-muted);\">(защищена)</span></span>\n");
buffer_append(buf, " </a>\n");
buffer_append(buf, " <div class=\"file-controls\">\n");
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"showPasswordForm('");
buffer_append(buf, entries[i].encoded_path);
buffer_append(buf, "', '");
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, " </div>\n");
buffer_append(buf, " </div>\n");
} else {
buffer_append(buf, " <div class=\"file-row\">\n");
char dir_link[512];
snprintf(dir_link, sizeof(dir_link),
" <a class=\"dir-link\" href=\"/cgi-bin/index.cgi?path=%s\">\n",
entries[i].encoded_path);
buffer_append(buf, dir_link);
buffer_append(buf, " <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);
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);
}
void print_template(const char *base_path, const char *display_path) {
FILE *file = fopen(g_config.template_path, "r");
if (!file) {
printf("Error: Cannot read template\n");
return;
}
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);
fwrite(output.data, 1, output.size, stdout);
buffer_free(&output);
}
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", &g_config) != 0) {
set_default_config(&g_config);
}
strcpy(g_params.sort_by, "name");
g_params.sort_order = 1;
g_params.search[0] = '\0';
g_params.recursive = 0;
char *query_string = getenv("QUERY_STRING");
char display_path[1024] = "";
char safe_display_path[1024] = "";
char base_path[1024];
strcpy(base_path, g_config.base_path);
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 ? path_end - path_start : strlen(path_start);
if (path_len > 0 && path_len < 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 ? sort_end - sort_start : strlen(sort_start);
if (sort_len > 0 && sort_len < sizeof(g_params.sort_by)) {
strncpy(g_params.sort_by, sort_start, sort_len);
g_params.sort_by[sort_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 ? search_end - search_start : strlen(search_start);
if (search_len > 0 && search_len < 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;
}
}
}
if (strstr(query_string, "password=")) {
char *error = strstr(query_string, "error=1");
if (!error) {
char folder_name[256] = "";
if (display_path[0]) {
char *last_slash = strrchr(display_path, '/');
if (last_slash) strcpy(folder_name, last_slash + 1);
else strcpy(folder_name, display_path);
if (is_folder_protected(folder_name)) {
char *pass_start = strstr(query_string, "password=");
if (pass_start) {
pass_start += 9;
char *pass_end = strchr(pass_start, '&');
int pass_len = pass_end ? pass_end - pass_start : strlen(pass_start);
if (pass_len > 0 && pass_len < 256) {
char pass_buf[256];
strncpy(pass_buf, pass_start, pass_len);
pass_buf[pass_len] = '\0';
char decoded_pass[256];
url_decode_enhanced(pass_buf, decoded_pass, sizeof(decoded_pass));
if (decoded_pass[0] == '\0') {
char encoded_path[1024];
url_encode(display_path, encoded_path, sizeof(encoded_path));
printf("Status: 302 Found\r\n");
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
return 0;
}
if (!check_folder_password(folder_name, decoded_pass)) {
char encoded_path[1024];
url_encode(display_path, encoded_path, sizeof(encoded_path));
printf("Status: 302 Found\r\n");
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
return 0;
}
} else {
char encoded_path[1024];
url_encode(display_path, encoded_path, sizeof(encoded_path));
printf("Status: 302 Found\r\n");
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
return 0;
}
}
}
}
}
}
}
printf("Content-type: text/html; charset=utf-8\n\n");
print_template(base_path, display_path);
return 0;
}