#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 #include #include #include #include #include #include #include #include #include #include #include #include #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; }