Files
Trashbox/index.c

1174 lines
49 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]; // "name", "size", "date"
int sort_order; // 1 = asc, -1 = desc
char search[256];
int recursive; // 1 = рекурсивный поиск, 0 = только текущая папка
} query_params_t;
query_params_t g_params;
// === ПОИСК БЕЗ УЧЁТА РЕГИСТРА (поддерживает UTF-8) ===
char* my_strcasestr(const char *haystack, const char *needle) {
if (!haystack || !needle || *needle == '\0') return (char*)haystack;
const char *h = haystack;
const char *n = needle;
size_t needle_len = strlen(needle);
while (*h) {
if (strncasecmp(h, n, needle_len) == 0) {
return (char*)h;
}
h++;
}
return NULL;
}
// === HTML ЭКРАНИРОВАНИЕ (защита от XSS) ===
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;
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 *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 == '%') {
// Проверяем, что есть ещё минимум 2 символа
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 link[4096];
char safe_token[512];
html_escape(token, safe_token, sizeof(safe_token));
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;
int 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);
}
if (entries[i].file_count > 0 && !is_recursive_search) {
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, " </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");
}
}
}
// Вывод файлов
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) {
// === ПАРСИМ PATH ===
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);
}
}
}
// === ПАРСИМ SORT ===
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';
}
}
// === ПАРСИМ SEARCH (отдельно от path!) ===
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;
}