436 lines
15 KiB
C
Executable File
436 lines
15 KiB
C
Executable File
// /home/romkazvo/www/cgi-bin/index.c
|
||
// Основной файловый сервер
|
||
#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>
|
||
|
||
#define MAX_ENTRIES 1000
|
||
#define TEMPLATE_PATH "/home/romkazvo/www/cgi-bin/template.html"
|
||
#define OUTPUT_BUFFER_SIZE 65536
|
||
|
||
typedef struct {
|
||
char name[256];
|
||
int is_dir;
|
||
long size;
|
||
char icon[8];
|
||
char encoded_path[1024];
|
||
char file_url[1024];
|
||
} entry_t;
|
||
|
||
typedef struct {
|
||
char *data;
|
||
size_t size;
|
||
size_t capacity;
|
||
} buffer_t;
|
||
|
||
void buffer_init(buffer_t *buf) {
|
||
buf->capacity = OUTPUT_BUFFER_SIZE;
|
||
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_append_size(buffer_t *buf, const char *str, size_t len) {
|
||
if (buf->size + len >= buf->capacity) {
|
||
buf->capacity *= 2;
|
||
buf->data = realloc(buf->data, buf->capacity);
|
||
}
|
||
memcpy(buf->data + buf->size, str, len);
|
||
buf->size += len;
|
||
}
|
||
|
||
void buffer_free(buffer_t *buf) {
|
||
free(buf->data);
|
||
}
|
||
|
||
const char* get_file_icon(const char* filename) {
|
||
const char *ext = strrchr(filename, '.');
|
||
if (!ext) return "📄";
|
||
|
||
ext++;
|
||
|
||
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
|
||
strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 ||
|
||
strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0)
|
||
return "🖼️";
|
||
|
||
if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 ||
|
||
strcasecmp(ext, "flac") == 0 || strcasecmp(ext, "ogg") == 0)
|
||
return "🎵";
|
||
|
||
if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "avi") == 0 ||
|
||
strcasecmp(ext, "mkv") == 0 || strcasecmp(ext, "mov") == 0)
|
||
return "🎬";
|
||
|
||
if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "rar") == 0 ||
|
||
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 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 size, char* buffer) {
|
||
if (size < 1024) {
|
||
snprintf(buffer, 32, "%ld 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 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 safe_path_join(char *result, size_t result_size, const char *base, const char *path) {
|
||
snprintf(result, result_size, "%s/%s", base, path);
|
||
}
|
||
|
||
void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
||
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];
|
||
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, token);
|
||
buffer_append(buf, link);
|
||
|
||
token = strtok(NULL, "/");
|
||
}
|
||
free(path_copy);
|
||
}
|
||
buffer_append(buf, "\n</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[MAX_ENTRIES];
|
||
int entry_count = 0;
|
||
int dir_count = 0, file_count = 0;
|
||
long long total_size = 0;
|
||
|
||
dir = opendir(base_path);
|
||
if (!dir) {
|
||
char alt_path[1024];
|
||
snprintf(alt_path, sizeof(alt_path), "/home/romkazvo/www");
|
||
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");
|
||
return;
|
||
}
|
||
}
|
||
|
||
while ((entry = readdir(dir)) != NULL && entry_count < MAX_ENTRIES) {
|
||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||
continue;
|
||
if (strcmp(entry->d_name, "cgi-bin") == 0)
|
||
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;
|
||
strcpy(entries[entry_count].icon, "📁");
|
||
|
||
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;
|
||
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
|
||
|
||
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url),
|
||
"%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
||
|
||
file_count++;
|
||
total_size += file_stat.st_size;
|
||
} 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");
|
||
return;
|
||
}
|
||
|
||
qsort(entries, entry_count, sizeof(entry_t), compare_entries);
|
||
|
||
buffer_append(buf, "<ul>\n");
|
||
|
||
if (dir_count > 0) {
|
||
char section_title[128];
|
||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", 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");
|
||
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\">");
|
||
buffer_append(buf, entries[i].name);
|
||
buffer_append(buf, "</span>\n");
|
||
buffer_append(buf, " </a>\n");
|
||
buffer_append(buf, " </li>\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (file_count > 0) {
|
||
char section_title[128];
|
||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
|
||
buffer_append(buf, section_title);
|
||
|
||
for (int i = 0; i < entry_count; i++) {
|
||
if (!entries[i].is_dir) {
|
||
char size_str[32];
|
||
format_size(entries[i].size, size_str);
|
||
|
||
buffer_append(buf, " <li>\n");
|
||
char file_link[512];
|
||
snprintf(file_link, sizeof(file_link),
|
||
" <a class=\"file-link\" href=\"/%s\" download>\n",
|
||
entries[i].file_url);
|
||
buffer_append(buf, file_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\">");
|
||
buffer_append(buf, entries[i].name);
|
||
buffer_append(buf, "</span>\n");
|
||
buffer_append(buf, " </a>\n");
|
||
buffer_append(buf, " <span class=\"size\">");
|
||
buffer_append(buf, size_str);
|
||
buffer_append(buf, "</span>\n");
|
||
buffer_append(buf, " </li>\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
buffer_append(buf, "</ul>\n");
|
||
|
||
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);
|
||
}
|
||
|
||
void print_template(const char *base_path, const char *display_path) {
|
||
FILE *file = fopen(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");
|
||
|
||
printf("Content-type: text/html; charset=utf-8\n\n");
|
||
|
||
char base_path[1024] = "/home/romkazvo/www";
|
||
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));
|
||
|
||
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), "/home/romkazvo/www", safe_display_path);
|
||
}
|
||
}
|
||
}
|
||
|
||
print_template(base_path, display_path);
|
||
return 0;
|
||
} |