diff options
| author | frosty <gabriel@bwaaa.monster> | 2026-09-16 16:32:06 -0400 |
|---|---|---|
| committer | frosty <gabriel@bwaaa.monster> | 2026-09-16 16:32:06 -0400 |
| commit | 4c8d764d171ec77ff626cec273c44ec7e7a995fc (patch) | |
| tree | dc36193e611ece53c8f7e74d1dea71f8d9e7004e /src/FileProcessor/FileProcessor.c | |
| download | yapssg-4c8d764d171ec77ff626cec273c44ec7e7a995fc.tar.gz | |
Diffstat (limited to 'src/FileProcessor/FileProcessor.c')
| -rw-r--r-- | src/FileProcessor/FileProcessor.c | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/src/FileProcessor/FileProcessor.c b/src/FileProcessor/FileProcessor.c new file mode 100644 index 0000000..d856fb4 --- /dev/null +++ b/src/FileProcessor/FileProcessor.c @@ -0,0 +1,396 @@ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 + +#include "FileProcessor/FileProcessor.h" + +#include "Logger/Logger.h" +#include "LuaEngine/LuaEngine.h" +#include "Path/Path.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <libxml/HTMLparser.h> +#include <libxml/HTMLtree.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#define COPY_BUFFER_SIZE 16384 + +static int has_html_extension(const char *path) { + const char *extension = strrchr(path, '.'); + + return extension != NULL && (strcasecmp(extension, ".html") == 0 || + strcasecmp(extension, ".htm") == 0); +} + +static void report_copy_error(const char *input_path, const char *output_path, + int error_number) { + logger_error("cannot copy %s to %s: %s", input_path, output_path, + strerror(error_number)); +} + +static int write_all(int file, const char *buffer, size_t length) { + size_t offset = 0; + + while (offset < length) { + ssize_t written = write(file, buffer + offset, length - offset); + + if (written > 0) { + offset += (size_t)written; + } else if (written == 0) { + errno = EIO; + return -1; + } else if (errno != EINTR) { + return -1; + } + } + return 0; +} + +static int copy_file_data(int input, int output) { + char buffer[COPY_BUFFER_SIZE]; + + for (;;) { + ssize_t count = read(input, buffer, sizeof(buffer)); + + if (count > 0 && write_all(output, buffer, (size_t)count) != 0) { + return -1; + } + if (count == 0) { + return 0; + } + if (count < 0 && errno != EINTR) { + return -1; + } + } +} + +static int copy_regular_file(const char *input_path, const char *output_path, + mode_t mode) { + int input = open(input_path, O_RDONLY); + int output; + int result; + int error_number; + + logger_debug("copying file %s -> %s", input_path, output_path); + + if (input < 0) { + report_copy_error(input_path, output_path, errno); + return -1; + } + + output = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, mode & 0777); + if (output < 0) { + error_number = errno; + close(input); + report_copy_error(input_path, output_path, error_number); + return -1; + } + + result = copy_file_data(input, output); + error_number = result == 0 ? 0 : errno; + + if (close(output) != 0 && result == 0) { + result = -1; + error_number = errno; + } + if (close(input) != 0 && result == 0) { + result = -1; + error_number = errno; + } + + if (result != 0) { + report_copy_error(input_path, output_path, error_number); + } + return result; +} + +int process_file(const char *input_path, const char *output_path) { + const int parse_options = HTML_PARSE_RECOVER | HTML_PARSE_NONET | + HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING; + struct stat status; + htmlDocPtr document; + int saved; + + if (stat(input_path, &status) != 0) { + logger_error("cannot inspect %s: %s", input_path, strerror(errno)); + return -1; + } + if (!S_ISREG(status.st_mode)) { + logger_error("input is not a regular file: %s", input_path); + return -1; + } + + logger_info("processing HTML %s -> %s", input_path, output_path); + document = htmlReadFile(input_path, NULL, parse_options); + if (document == NULL) { + logger_error("unable to parse HTML file: %s", input_path); + return -1; + } + + if (process_html_document(document, input_path) != 0) { + xmlFreeDoc(document); + return -1; + } + + saved = htmlSaveFileFormat(output_path, document, "UTF-8", 1); + xmlFreeDoc(document); + if (saved < 0) { + logger_error("unable to write HTML file: %s", output_path); + return -1; + } + return 0; +} + +static int copy_symlink(const char *input_path, const char *output_path, + const struct stat *status) { + size_t capacity = status->st_size > 0 ? (size_t)status->st_size + 1 : 256; + char *target = malloc(capacity + 1); + ssize_t length = -1; + + logger_debug("copying symbolic link %s -> %s", input_path, output_path); + + if (target == NULL) { + logger_error("unable to allocate symbolic-link buffer"); + return -1; + } + + while (length < 0) { + ssize_t read_length = readlink(input_path, target, capacity); + + if (read_length < 0) { + logger_error("cannot read symbolic link %s: %s", input_path, + strerror(errno)); + free(target); + return -1; + } + if ((size_t)read_length < capacity) { + length = read_length; + } else { + size_t larger_capacity; + char *larger_target; + + if (capacity > (SIZE_MAX - 1) / 2) { + logger_error("unable to allocate symbolic-link buffer"); + free(target); + return -1; + } + larger_capacity = capacity * 2; + larger_target = realloc(target, larger_capacity + 1); + if (larger_target == NULL) { + logger_error("unable to allocate symbolic-link buffer"); + free(target); + return -1; + } + target = larger_target; + capacity = larger_capacity; + } + } + + target[length] = '\0'; + if (symlink(target, output_path) != 0) { + logger_error("cannot copy symbolic link %s: %s", input_path, + strerror(errno)); + free(target); + return -1; + } + + free(target); + return 0; +} + +static int copy_tree(const char *input_path, const char *output_path) { + struct stat status; + DIR *directory; + int result = 0; + + if (lstat(input_path, &status) != 0) { + logger_error("cannot inspect %s: %s", input_path, strerror(errno)); + return -1; + } + if (S_ISLNK(status.st_mode)) { + return copy_symlink(input_path, output_path, &status); + } + if (S_ISREG(status.st_mode)) { + return has_html_extension(input_path) + ? process_file(input_path, output_path) + : copy_regular_file(input_path, output_path, status.st_mode); + } + if (!S_ISDIR(status.st_mode)) { + logger_error("unsupported file type: %s", input_path); + return -1; + } + + if (mkdir(output_path, status.st_mode & 0777) != 0) { + logger_error("cannot create directory %s: %s", output_path, + strerror(errno)); + return -1; + } + + directory = opendir(input_path); + if (directory == NULL) { + logger_error("cannot open directory %s: %s", input_path, strerror(errno)); + return -1; + } + + while (result == 0) { + struct dirent *entry; + char *input_child; + char *output_child; + + errno = 0; + entry = readdir(directory); + if (entry == NULL) { + if (errno != 0) { + logger_error("cannot read directory %s: %s", input_path, + strerror(errno)); + result = -1; + } + break; + } + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + input_child = path_join(input_path, entry->d_name); + output_child = path_join(output_path, entry->d_name); + if (input_child == NULL || output_child == NULL) { + logger_error("unable to allocate child path"); + result = -1; + } else { + result = copy_tree(input_child, output_child); + } + free(input_child); + free(output_child); + } + + if (closedir(directory) != 0 && result == 0) { + logger_error("cannot close directory %s: %s", input_path, strerror(errno)); + result = -1; + } + return result; +} + +static int remove_tree(const char *path) { + struct stat status; + DIR *directory; + int result = 0; + + if (lstat(path, &status) != 0) { + if (errno == ENOENT) { + return 0; + } + logger_error("cannot inspect output %s: %s", path, strerror(errno)); + return -1; + } + + if (!S_ISDIR(status.st_mode) || S_ISLNK(status.st_mode)) { + if (unlink(path) != 0) { + logger_error("cannot remove output %s: %s", path, strerror(errno)); + return -1; + } + return 0; + } + + directory = opendir(path); + if (directory == NULL) { + logger_error("cannot open output directory %s: %s", path, strerror(errno)); + return -1; + } + + while (result == 0) { + struct dirent *entry; + char *child; + + errno = 0; + entry = readdir(directory); + if (entry == NULL) { + if (errno != 0) { + logger_error("cannot read output directory %s: %s", path, + strerror(errno)); + result = -1; + } + break; + } + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + child = path_join(path, entry->d_name); + if (child == NULL) { + logger_error("unable to allocate output child path"); + result = -1; + } else { + result = remove_tree(child); + } + free(child); + } + + if (closedir(directory) != 0 && result == 0) { + logger_error("cannot close output directory %s: %s", path, strerror(errno)); + result = -1; + } + if (result == 0 && rmdir(path) != 0) { + logger_error("cannot remove output directory %s: %s", path, + strerror(errno)); + result = -1; + } + return result; +} + +int process_directory(const char *input_path, const char *output_path, + int overwrite_output) { + struct stat status; + struct stat output_status; + char *resolved_input; + char *resolved_output; + int result; + + if (stat(input_path, &status) != 0) { + logger_error("cannot inspect %s: %s", input_path, strerror(errno)); + return -1; + } + if (!S_ISDIR(status.st_mode)) { + logger_error("input is not a directory: %s", input_path); + return -1; + } + + resolved_input = realpath(input_path, NULL); + if (lstat(output_path, &output_status) == 0 && + !S_ISLNK(output_status.st_mode)) { + resolved_output = realpath(output_path, NULL); + } else { + resolved_output = path_resolve_new(output_path); + } + if (resolved_input == NULL || resolved_output == NULL) { + logger_error("cannot resolve input or output path"); + free(resolved_input); + free(resolved_output); + return -1; + } + + if (path_is_within(resolved_input, resolved_output)) { + logger_error("output directory cannot be inside input: %s", output_path); + result = -1; + } else if (overwrite_output && + path_is_within(resolved_output, resolved_input)) { + logger_error("output directory cannot contain input: %s", output_path); + result = -1; + } else if (overwrite_output && remove_tree(output_path) != 0) { + result = -1; + } else { + logger_info("processing directory %s -> %s", input_path, output_path); + result = copy_tree(input_path, output_path); + } + + free(resolved_input); + free(resolved_output); + return result; +} |
