aboutsummaryrefslogtreecommitdiff
path: root/src/FileProcessor/FileProcessor.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/FileProcessor/FileProcessor.c')
-rw-r--r--src/FileProcessor/FileProcessor.c396
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;
+}