// /home/romkazvo/www/cgi-bin/index.c #include #include #include #include #include #include #include #include #include #include // === КОНФИГУРАЦИЯ === 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; // Простой парсер 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 { 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; if (strcmp(folder, folder_name) == 0) { fclose(f); return strcmp(pass, password) == 0; } } fclose(f); return 1; } int is_folder_protected(const char *folder_name) { if (!folder_name || folder_name[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'; if (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)); } int compare_entries(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; return strcasecmp(entryA->name, entryB->name); } 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) { *p++ = c; decoded_len++; } else if (c >= 0x20 || c == 0x0A || c == 0x0D) { *p++ = c; decoded_len++; } else { *p++ = '_'; decoded_len++; } src += 3; } else { *p++ = *src++; decoded_len++; } } 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, "
\n"); buffer_append(buf, " 🏠 Главная"); 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]; snprintf(link, sizeof(link), "%s", encoded, token); buffer_append(buf, link); token = strtok(NULL, "/"); } free(path_copy); } buffer_append(buf, "\n
\n"); } void print_password_form(buffer_t *buf, const char *folder_name) { buffer_append(buf, "
\n"); buffer_append(buf, "
\n"); buffer_append(buf, "
🔒
\n"); buffer_append(buf, "

Папка защищена паролем

\n"); buffer_append(buf, "

Введите пароль для доступа к папке "); buffer_append(buf, folder_name); buffer_append(buf, "

\n"); buffer_append(buf, "
\n"); buffer_append(buf, " \n"); buffer_append(buf, " \n"); buffer_append(buf, " \n"); buffer_append(buf, "
\n"); buffer_append(buf, "

"); char *query_string = getenv("QUERY_STRING"); if (query_string && strstr(query_string, "error=1")) { buffer_append(buf, "❌ Неверный пароль! Попробуйте снова."); } buffer_append(buf, "

\n"); buffer_append(buf, " ← Вернуться на главную\n"); buffer_append(buf, "
\n"); buffer_append(buf, "
\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, "
Ошибка выделения памяти
"); return; } int entry_count = 0; int dir_count = 0, file_count = 0; long long total_size = 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; } } } 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, "
\n"); buffer_append(buf, "
📁
\n"); buffer_append(buf, "

Ошибка открытия директории

\n"); char error_msg[512]; snprintf(error_msg, sizeof(error_msg), "

Путь: %s

\n", base_path); buffer_append(buf, error_msg); snprintf(error_msg, sizeof(error_msg), "

Ошибка: %s

\n", strerror(errno)); buffer_append(buf, error_msg); buffer_append(buf, "

← Вернуться на главную

\n"); buffer_append(buf, "
\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; snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name); total_size += file_stat.st_size; file_count++; } else { continue; } entry_count++; } } closedir(dir); if (entry_count == 0) { buffer_append(buf, "
\n"); buffer_append(buf, "
📄
\n"); buffer_append(buf, "

Здесь пусто

\n"); buffer_append(buf, "

В этой директории нет файлов или папок

\n"); buffer_append(buf, "
\n"); free(entries); return; } qsort(entries, entry_count, sizeof(entry_t), compare_entries); buffer_append(buf, "\n"); char total_size_str[32]; format_size(total_size, total_size_str); char stats[256]; snprintf(stats, sizeof(stats), "
\n Папки: %d | Файлы: %d | Общий размер: %s\n
\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, "")) { print_breadcrumb(&output, display_path); } else if (strstr(line, "")) { 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); } printf("Content-type: text/html; charset=utf-8\n\n"); char base_path[1024]; strcpy(base_path, g_config.base_path); char display_path[1024] = ""; char safe_display_path[1024] = ""; char *query_string = getenv("QUERY_STRING"); 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); } } } 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("Location: /cgi-bin/index.cgi?path=%s&error=1\n\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("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path); return 0; } } else { 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; } } } } } } } print_template(base_path, display_path); return 0; }