diff --git a/Makefile b/Makefile index 8021b18..e22f56c 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,23 @@ -# Makefile для Trashbox CGI CC = gcc -CFLAGS = -Wall -Wextra -O2 +CFLAGS = -Wall -Wextra -O2 -I./src TARGETS = index.cgi style.cgi status.cgi +SRCS_INDEX = src/main.c src/config.c src/auth.c src/fs.c src/render.c src/utils.c +OBJS_INDEX = $(SRCS_INDEX:.c=.o) + all: $(TARGETS) -index.cgi: index.c +index.cgi: $(OBJS_INDEX) + $(CC) $(CFLAGS) -o $@ $^ + +style.cgi: src/style.c $(CC) $(CFLAGS) -o $@ $< -style.cgi: style.c - $(CC) $(CFLAGS) -o $@ $< - -status.cgi: status.c +status.cgi: src/status.c $(CC) $(CFLAGS) -o $@ $< clean: - rm -f $(TARGETS) + rm -f $(OBJS_INDEX) $(TARGETS) install: all chmod +x $(TARGETS) diff --git a/README.md b/README.md index a79c861..30c8180 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ sudo apt install nginx busybox gcc ```bash mkdir -p /home/romkazvo/www/cgi-bin cd /home/romkazvo/www/cgi-bin +git clone https://git.mashup.su/RomkaZVO/Trashbox.git ``` ### Компиляция CGI ```bash -sudo sh build.sh +cd src +make +sudo make install ``` ### Конфигурация Nginx Файл конфигурации `/etc/nginx/sites-available/trashbox`:
@@ -28,4 +31,4 @@ busybox httpd -p 127.0.0.1:8050 -h /home/romkazvo/www cat > /home/romkazvo/www/cgi-bin/.htpasswd << EOF secret:mysecretpassword EOF -``` \ No newline at end of file +``` diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..5a429ee Binary files /dev/null and b/index.cgi differ diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..1f8cedb --- /dev/null +++ b/src/auth.c @@ -0,0 +1,52 @@ +#include +#include +#include "auth.h" +#include "config.h" + +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; +} diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..f959f56 --- /dev/null +++ b/src/auth.h @@ -0,0 +1,7 @@ +#ifndef AUTH_H +#define AUTH_H + +int is_folder_protected(const char *folder_path); +int check_folder_password(const char *folder_name, const char *password); + +#endif diff --git a/src/auth.o b/src/auth.o new file mode 100644 index 0000000..a9997cd Binary files /dev/null and b/src/auth.o differ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..7698fdc --- /dev/null +++ b/src/config.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "config.h" + +config_t g_config; + +int load_config(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) return -1; + + char line[512]; + char current_section[64] = ""; + + while (fgets(line, sizeof(line), f)) { + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0' || *p == '#' || *p == ';') continue; + + if (*p == '[') { + p++; + char *end = strchr(p, ']'); + if (end) { + *end = '\0'; + strncpy(current_section, p, sizeof(current_section) - 1); + current_section[sizeof(current_section) - 1] = '\0'; + } + continue; + } + + char *eq = strchr(p, '='); + if (!eq) continue; + *eq = '\0'; + char *key = p; + char *value = eq + 1; + + char *end_key = key + strlen(key) - 1; + while (end_key > key && (*end_key == ' ' || *end_key == '\t')) { + *end_key = '\0'; + end_key--; + } + + while (*value == ' ' || *value == '\t') value++; + char *end_val = value + strlen(value) - 1; + while (end_val > value && (*end_val == ' ' || *end_val == '\t' || *end_val == '\r' || *end_val == '\n')) { + *end_val = '\0'; + end_val--; + } + + if (strcmp(current_section, "paths") == 0) { + if (strcmp(key, "base_path") == 0) strncpy(g_config.base_path, value, sizeof(g_config.base_path) - 1); + else if (strcmp(key, "template_path") == 0) strncpy(g_config.template_path, value, sizeof(g_config.template_path) - 1); + else if (strcmp(key, "passwd_file") == 0) strncpy(g_config.passwd_file, value, sizeof(g_config.passwd_file) - 1); + } + else if (strcmp(current_section, "limits") == 0) { + if (strcmp(key, "max_entries") == 0) g_config.max_entries = atoi(value); + } + else if (strcmp(current_section, "hidden") == 0) { + if (strcmp(key, "dirs") == 0) { + char *token = strtok(value, ","); + g_config.hidden_dirs_count = 0; + while (token && g_config.hidden_dirs_count < 10) { + while (*token == ' ') token++; + strncpy(g_config.hidden_dirs[g_config.hidden_dirs_count], token, 255); + g_config.hidden_dirs[g_config.hidden_dirs_count][255] = '\0'; + g_config.hidden_dirs_count++; + token = strtok(NULL, ","); + } + } + else if (strcmp(key, "files") == 0) { + char *token = strtok(value, ","); + g_config.hidden_files_count = 0; + while (token && g_config.hidden_files_count < 10) { + while (*token == ' ') token++; + strncpy(g_config.hidden_files[g_config.hidden_files_count], token, 255); + g_config.hidden_files[g_config.hidden_files_count][255] = '\0'; + g_config.hidden_files_count++; + token = strtok(NULL, ","); + } + } + } + else if (strcmp(current_section, "preview") == 0) { + if (strcmp(key, "image") == 0) strncpy(g_config.preview_image, value, sizeof(g_config.preview_image) - 1); + else if (strcmp(key, "text") == 0) strncpy(g_config.preview_text, value, sizeof(g_config.preview_text) - 1); + else if (strcmp(key, "audio") == 0) strncpy(g_config.preview_audio, value, sizeof(g_config.preview_audio) - 1); + else if (strcmp(key, "video") == 0) strncpy(g_config.preview_video, value, sizeof(g_config.preview_video) - 1); + else if (strcmp(key, "pdf") == 0) strncpy(g_config.preview_pdf, value, sizeof(g_config.preview_pdf) - 1); + } + } + + fclose(f); + return 0; +} + +void set_default_config(void) { + strcpy(g_config.base_path, "/home/romkazvo/www"); + strcpy(g_config.template_path, "/home/romkazvo/www/cgi-bin/template.html"); + strcpy(g_config.passwd_file, "/home/romkazvo/www/cgi-bin/.htpasswd"); + g_config.max_entries = 1000; + g_config.hidden_dirs_count = 0; + g_config.hidden_files_count = 0; + strcpy(g_config.preview_image, "jpg,jpeg,png,gif,webp,bmp,svg,ico"); + strcpy(g_config.preview_text, "txt,md,html,htm,css,js,json,xml,csv"); + strcpy(g_config.preview_audio, "mp3,wav,ogg,flac,m4a,aac"); + strcpy(g_config.preview_video, "mp4,webm,ogv,mov,avi,mkv"); + strcpy(g_config.preview_pdf, "pdf"); +} + +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; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..8b1dacc --- /dev/null +++ b/src/config.h @@ -0,0 +1,26 @@ +#ifndef CONFIG_H +#define CONFIG_H + +typedef struct { + char base_path[1024]; + char template_path[1024]; + char passwd_file[1024]; + int max_entries; + char hidden_dirs[10][256]; + int hidden_dirs_count; + char hidden_files[10][256]; + int hidden_files_count; + char preview_image[512]; + char preview_text[512]; + char preview_audio[512]; + char preview_video[512]; + char preview_pdf[512]; +} config_t; + +extern config_t g_config; + +int load_config(const char *path); +void set_default_config(void); +int is_string_in_list(const char *str, const char *list); + +#endif diff --git a/src/config.o b/src/config.o new file mode 100644 index 0000000..4dca6db Binary files /dev/null and b/src/config.o differ diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..0921b70 --- /dev/null +++ b/src/fs.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "config.h" +#include "auth.h" +#include "utils.h" + +query_params_t g_params; + +int is_safe_path(const char *path) { + if (!path) return 0; + if (strstr(path, "..")) return 0; + if (strstr(path, "./")) return 0; + if (path[0] == '/') return 0; + if (strstr(path, "//")) return 0; + return 1; +} + +void safe_path_join(char *result, size_t result_size, const char *base, const char *path) { + if (!is_safe_path(path)) { + snprintf(result, result_size, "%s", base); + return; + } + snprintf(result, result_size, "%s/%s", base, path); +} + +int count_files_in_dir(const char *path) { + DIR *dir = opendir(path); + if (!dir) return 0; + struct dirent *entry; + int count = 0; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + if (entry->d_name[0] == '.') continue; + int hidden = 0; + for (int i = 0; i < g_config.hidden_files_count; i++) { + if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; } + } + if (hidden) continue; + count++; + } + closedir(dir); + return count; +} + +long long get_dir_size(const char *path) { + DIR *dir = opendir(path); + if (!dir) return 0; + struct dirent *entry; + struct stat statbuf; + char fullpath[1024]; + long long size = 0; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name); + if (stat(fullpath, &statbuf) == 0) { + if (S_ISDIR(statbuf.st_mode)) size += get_dir_size(fullpath); + else if (S_ISREG(statbuf.st_mode)) size += statbuf.st_size; + } + } + closedir(dir); + return size; +} + +const char* get_file_icon(const char* filename) { + const char *ext = strrchr(filename, '.'); + if (!ext) return "📄"; + ext++; + if (is_string_in_list(ext, g_config.preview_image)) return "🖼️"; + if (is_string_in_list(ext, g_config.preview_audio)) return "🎵"; + if (is_string_in_list(ext, g_config.preview_video)) return "🎬"; + if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 || + strcasecmp(ext, "7z") == 0 || strcasecmp(ext, "tar") == 0 || + strcasecmp(ext, "gz") == 0) return "📦"; + if (strcasecmp(ext, "pdf") == 0) return "📕"; + if (strcasecmp(ext, "doc") == 0 || strcasecmp(ext, "docx") == 0) return "📘"; + if (strcasecmp(ext, "xls") == 0 || strcasecmp(ext, "xlsx") == 0) return "📗"; + if (strcasecmp(ext, "txt") == 0) return "📝"; + return "📄"; +} + +int is_previewable(const char* filename) { + const char *ext = strrchr(filename, '.'); + if (!ext) return 0; + ext++; + return (is_string_in_list(ext, g_config.preview_image) || + is_string_in_list(ext, g_config.preview_text) || + is_string_in_list(ext, g_config.preview_audio) || + is_string_in_list(ext, g_config.preview_video) || + is_string_in_list(ext, g_config.preview_pdf)); +} + +void search_recursive(const char *base_path, const char *display_path, const char *search_term, entry_t *results, int *count, int max_results) { + DIR *dir = opendir(base_path); + if (!dir) return; + struct dirent *entry; + struct stat file_stat; + char full_path[1024]; + char display_subpath[1024]; + while ((entry = readdir(dir)) != NULL && *count < max_results) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + if (entry->d_name[0] == '.') continue; + int hidden = 0; + for (int i = 0; i < g_config.hidden_dirs_count; i++) { + if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; } + } + for (int i = 0; i < g_config.hidden_files_count; i++) { + if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; } + } + if (hidden) continue; + snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name); + if (display_path[0] == '\0') { + snprintf(display_subpath, sizeof(display_subpath), "%s", entry->d_name); + } else { + snprintf(display_subpath, sizeof(display_subpath), "%s/%s", display_path, entry->d_name); + } + if (is_folder_protected(display_subpath) || is_folder_protected(entry->d_name)) { + continue; + } + if (stat(full_path, &file_stat) == 0) { + if (my_strcasestr(entry->d_name, search_term) != NULL) { + entry_t *result = &results[*count]; + 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; +} diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..ee43d6f --- /dev/null +++ b/src/fs.h @@ -0,0 +1,37 @@ +#ifndef FS_H +#define FS_H + +#include + +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 sort_by[16]; + int sort_order; + char search[256]; + int recursive; +} query_params_t; + +extern query_params_t g_params; + +int is_safe_path(const char *path); +void safe_path_join(char *result, size_t result_size, const char *base, const char *path); +int count_files_in_dir(const char *path); +long long get_dir_size(const char *path); +const char* get_file_icon(const char* filename); +int is_previewable(const char* filename); +void search_recursive(const char *base_path, const char *display_path, const char *search_term, entry_t *results, int *count, int max_results); +int compare_entries_sorted(const void *a, const void *b); + +#endif diff --git a/src/fs.o b/src/fs.o new file mode 100644 index 0000000..69aa0b4 Binary files /dev/null and b/src/fs.o differ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d84512b --- /dev/null +++ b/src/main.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include "config.h" +#include "auth.h" +#include "fs.h" +#include "render.h" +#include "utils.h" + +int main() { + setlocale(LC_ALL, "en_US.UTF-8"); + setlocale(LC_CTYPE, "en_US.UTF-8"); + + if (load_config("/home/romkazvo/www/cgi-bin/config.ini") != 0) { + set_default_config(); + } + + strcpy(g_params.sort_by, "name"); + g_params.sort_order = 1; + g_params.search[0] = '\0'; + g_params.recursive = 0; + + 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 ? (int)(path_end - path_start) : (int)strlen(path_start); + if (path_len > 0 && path_len < (int)sizeof(display_path) - 1) { + char encoded_path[1024]; + strncpy(encoded_path, path_start, path_len); + encoded_path[path_len] = '\0'; + url_decode_enhanced(encoded_path, display_path, sizeof(display_path)); + if (!is_safe_path(display_path)) { + display_path[0] = '\0'; + strcpy(base_path, g_config.base_path); + } else { + strncpy(safe_display_path, display_path, sizeof(safe_display_path) - 1); + safe_display_path[sizeof(safe_display_path) - 1] = '\0'; + safe_path_join(base_path, sizeof(base_path), g_config.base_path, safe_display_path); + } + } + } + + char *sort_start = strstr(query_string, "sort="); + if (sort_start) { + sort_start += 5; + char *sort_end = strchr(sort_start, '&'); + int sort_len = sort_end ? (int)(sort_end - sort_start) : (int)strlen(sort_start); + if (sort_len > 0 && sort_len < (int)sizeof(g_params.sort_by)) { + strncpy(g_params.sort_by, sort_start, sort_len); + g_params.sort_by[sort_len] = '\0'; + } + } + + char *search_start = strstr(query_string, "search="); + if (search_start) { + search_start += 7; + char *search_end = strchr(search_start, '&'); + int search_len = search_end ? (int)(search_end - search_start) : (int)strlen(search_start); + if (search_len > 0 && search_len < (int)sizeof(g_params.search) - 1) { + char encoded_search[256]; + strncpy(encoded_search, search_start, search_len); + encoded_search[search_len] = '\0'; + url_decode_enhanced(encoded_search, g_params.search, sizeof(g_params.search)); + if (g_params.search[0] != '\0') { + g_params.recursive = 1; + } + } + } + + 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 ? (int)(pass_end - pass_start) : (int)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; +} diff --git a/src/main.o b/src/main.o new file mode 100644 index 0000000..81948f1 Binary files /dev/null and b/src/main.o differ diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..e965b4f --- /dev/null +++ b/src/render.c @@ -0,0 +1,482 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "render.h" +#include "config.h" +#include "auth.h" +#include "fs.h" +#include "utils.h" + +extern query_params_t g_params; + +void buffer_init(buffer_t *buf) { + buf->capacity = 65536; + buf->data = malloc(buf->capacity); + buf->size = 0; +} + +void buffer_append(buffer_t *buf, const char *str) { + size_t len = strlen(str); + if (buf->size + len >= buf->capacity) { + buf->capacity *= 2; + buf->data = realloc(buf->data, buf->capacity); + } + memcpy(buf->data + buf->size, str, len); + buf->size += len; +} + +void buffer_free(buffer_t *buf) { free(buf->data); } + +void 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 safe_token[512]; + html_escape(token, safe_token, sizeof(safe_token)); + char link[4096]; + snprintf(link, sizeof(link), "%s", encoded, safe_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, "

Введите пароль для доступа к папке "); + char safe_name[512]; + html_escape(folder_name, safe_name, sizeof(safe_name)); + buffer_append(buf, safe_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, 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 ? (int)(pass_end - pass_start) : (int)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, "
\n"); + buffer_append(buf, "
🔍
\n"); + buffer_append(buf, "

Ничего не найдено

\n"); + buffer_append(buf, "

По запросу \""); + char safe_search[512]; + html_escape(g_params.search, safe_search, sizeof(safe_search)); + buffer_append(buf, safe_search); + buffer_append(buf, "\" ничего не найдено

\n"); + buffer_append(buf, "
\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, "
\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; + 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, "
\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_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, "
\n"); + 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, "
\n"); + 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, "
\n"); + if (is_recursive_search) { + buffer_append(buf, "
"); + buffer_append(buf, "🔍 Результаты поиска по всему сайту: "); + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%d", entry_count); + buffer_append(buf, tmp); + buffer_append(buf, " файлов/папок найдено
\n"); + } + buffer_append(buf, "\n"); + if (is_recursive_search) { + char stats[256]; + snprintf(stats, sizeof(stats), + "
\n Найдено: папок %d | файлов %d\n
\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), + "
\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); +} diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..12a0752 --- /dev/null +++ b/src/render.h @@ -0,0 +1,17 @@ +#ifndef RENDER_H +#define RENDER_H + +#include "fs.h" + +typedef struct { + char *data; + size_t size; + size_t capacity; +} buffer_t; + +void buffer_init(buffer_t *buf); +void buffer_append(buffer_t *buf, const char *str); +void buffer_free(buffer_t *buf); +void print_template(const char *base_path, const char *display_path); + +#endif diff --git a/src/render.o b/src/render.o new file mode 100644 index 0000000..b5609ba Binary files /dev/null and b/src/render.o differ diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..a674bdc --- /dev/null +++ b/src/status.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +long get_nginx_memory() { + long total_kb = 0; + char command[] = "ps -o rss= -C nginx 2>/dev/null"; + + FILE *ps = popen(command, "r"); + if (!ps) return 0; + + char line[64]; + while (fgets(line, sizeof(line), ps)) { + long kb; + if (sscanf(line, "%ld", &kb) == 1) { + total_kb += kb; + } + } + pclose(ps); + + return total_kb; +} + +int get_process_memory(const char *process_name, long *memory_kb) { + char command[256]; + snprintf(command, sizeof(command), "ps -o rss= -C %s 2>/dev/null | head -1", process_name); + + FILE *ps = popen(command, "r"); + if (!ps) return 0; + + int result = fscanf(ps, "%ld", memory_kb); + pclose(ps); + + return result == 1; +} + +int main() { + printf("Content-type: text/plain; charset=utf-8\n\n"); + + long busybox_kb = 0, nginx_kb = 0; + + get_process_memory("busybox", &busybox_kb); + nginx_kb = get_nginx_memory(); + + printf("BusyBox: %ld KB | Nginx: %ld MB | Всего: %.1f MB", + busybox_kb, + nginx_kb / 1024, + (busybox_kb + nginx_kb) / 1024.0); + + return 0; +} diff --git a/src/style.c b/src/style.c new file mode 100644 index 0000000..818e8c1 --- /dev/null +++ b/src/style.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +int main() { + char css_path[1024] = "/home/romkazvo/www/cgi-bin/style.css"; + struct stat st; + + if (stat(css_path, &st) != 0) { + printf("Status: 404 Not Found\n"); + printf("Content-type: text/plain\n\n"); + printf("CSS file not found\n"); + return 1; + } + + printf("Content-type: text/css\n"); + printf("Cache-Control: public, max-age=3600\n\n"); + + FILE *css_file = fopen(css_path, "r"); + if (!css_file) { + printf("Status: 500 Internal Server Error\n"); + printf("Content-type: text/plain\n\n"); + printf("Cannot open CSS file\n"); + return 1; + } + + char buffer[4096]; + size_t bytes_read; + while ((bytes_read = fread(buffer, 1, sizeof(buffer), css_file)) > 0) { + fwrite(buffer, 1, bytes_read, stdout); + } + + fclose(css_file); + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..d1ee12d --- /dev/null +++ b/src/utils.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include "utils.h" + +char* my_strcasestr(const char *haystack, const char *needle) { + if (!haystack || !needle || *needle == '\0') return (char*)haystack; + size_t needle_len = strlen(needle); + while (*haystack) { + if (strncasecmp(haystack, needle, needle_len) == 0) return (char*)haystack; + haystack++; + } + return NULL; +} + +void html_escape(const char *src, char *dst, size_t dst_size) { + if (!src || !dst || dst_size == 0) return; + char *p = dst; + size_t i = 0; + while (*src && i < dst_size - 6) { + switch (*src) { + case '<': strcpy(p, "<"); p += 4; i += 4; break; + case '>': strcpy(p, ">"); p += 4; i += 4; break; + case '"': strcpy(p, """); p += 6; i += 6; break; + case '&': strcpy(p, "&"); p += 5; i += 5; break; + default: *p++ = *src; i++; break; + } + src++; + } + *p = '\0'; +} + +void format_size(long long size, char* buffer) { + if (size < 1024) snprintf(buffer, 32, "%lld B", size); + else if (size < 1024 * 1024) snprintf(buffer, 32, "%.1f KB", size / 1024.0); + else if (size < 1024 * 1024 * 1024) snprintf(buffer, 32, "%.1f MB", size / (1024.0 * 1024.0)); + else snprintf(buffer, 32, "%.1f GB", size / (1024.0 * 1024.0 * 1024.0)); +} + +void format_date(time_t mtime, char* buffer, size_t size) { + struct tm *tm_info = localtime(&mtime); + strftime(buffer, size, "%d.%m.%Y", tm_info); +} + +void url_encode(const char *src, char *dst, size_t dst_size) { + static const char *hex = "0123456789ABCDEF"; + char *p = dst; + size_t i = 0; + while (*src && i < dst_size - 3) { + unsigned char c = (unsigned char)*src; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || strchr("-_.~", c)) { + *p++ = c; i++; + } else if (c == ' ') { + *p++ = '%'; *p++ = '2'; *p++ = '0'; i += 3; + } else { + *p++ = '%'; *p++ = hex[(c >> 4) & 0xF]; *p++ = hex[c & 0xF]; i += 3; + } + src++; + } + *p = '\0'; +} + +void url_decode_enhanced(const char *src, char *dst, size_t dst_size) { + char *p = dst; + size_t decoded_len = 0; + while (*src && decoded_len < dst_size - 1) { + if (*src == '%') { + if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) { + char hex[3] = {src[1], src[2], '\0'}; + unsigned char c = (unsigned char)strtol(hex, NULL, 16); + if (c >= 0x80 || c >= 0x20 || c == 0x0A || c == 0x0D) { + *p++ = c; + decoded_len++; + } else { + *p++ = '_'; + decoded_len++; + } + src += 3; + } else { + src++; + } + } else if (*src == '+') { + *p++ = ' '; + decoded_len++; + src++; + } else { + *p++ = *src++; + decoded_len++; + } + } + *p = '\0'; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..0301302 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +void url_encode(const char *src, char *dst, size_t dst_size); +void url_decode_enhanced(const char *src, char *dst, size_t dst_size); +void html_escape(const char *src, char *dst, size_t dst_size); +char* my_strcasestr(const char *haystack, const char *needle); +void format_size(long long size, char* buffer); +void format_date(time_t mtime, char* buffer, size_t size); + +#endif diff --git a/src/utils.o b/src/utils.o new file mode 100644 index 0000000..a65a653 Binary files /dev/null and b/src/utils.o differ diff --git a/status.cgi b/status.cgi new file mode 100755 index 0000000..09ddee7 Binary files /dev/null and b/status.cgi differ diff --git a/style.cgi b/style.cgi new file mode 100755 index 0000000..833b3ae Binary files /dev/null and b/style.cgi differ