diff options
Diffstat (limited to 'src/Path')
| -rw-r--r-- | src/Path/Path.c | 185 | ||||
| -rw-r--r-- | src/Path/Path.h | 10 |
2 files changed, 195 insertions, 0 deletions
diff --git a/src/Path/Path.c b/src/Path/Path.c new file mode 100644 index 0000000..8a0ab2b --- /dev/null +++ b/src/Path/Path.c @@ -0,0 +1,185 @@ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 + +#include "Path/Path.h" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static const char output_prefix[] = "yapssg_"; + +static int checked_add(size_t left, size_t right, size_t *sum) { + if (left > SIZE_MAX - right) { + return -1; + } + *sum = left + right; + return 0; +} + +static char *duplicate_string(const char *value) { + size_t length = strlen(value); + char *copy = malloc(length + 1); + + if (copy != NULL) { + memcpy(copy, value, length + 1); + } + return copy; +} + +char *path_join(const char *left, const char *right) { + size_t left_length = strlen(left); + size_t right_length = strlen(right); + int needs_separator = left_length > 0 && left[left_length - 1] != '/'; + size_t path_length; + size_t allocation_size; + char *path; + + if (checked_add(left_length, (size_t)needs_separator, &path_length) != 0 || + checked_add(path_length, right_length, &path_length) != 0 || + checked_add(path_length, 1, &allocation_size) != 0) { + return NULL; + } + + path = malloc(allocation_size); + if (path == NULL) { + return NULL; + } + memcpy(path, left, left_length); + if (needs_separator) { + path[left_length++] = '/'; + } + memcpy(path + left_length, right, right_length + 1); + return path; +} + +char *path_make_default_output(const char *input_path) { + const char *end = input_path + strlen(input_path); + const char *base; + size_t parent_length; + size_t base_length; + size_t prefix_length = sizeof(output_prefix) - 1; + size_t output_length; + size_t allocation_size; + char *output_path; + + while (end > input_path + 1 && end[-1] == '/') { + --end; + } + base = end; + while (base > input_path && base[-1] != '/') { + --base; + } + + parent_length = (size_t)(base - input_path); + base_length = (size_t)(end - base); + if (base_length == 0) { + return NULL; + } + + if (checked_add(parent_length, prefix_length, &output_length) != 0 || + checked_add(output_length, base_length, &output_length) != 0 || + checked_add(output_length, 1, &allocation_size) != 0) { + return NULL; + } + + output_path = malloc(allocation_size); + if (output_path == NULL) { + return NULL; + } + memcpy(output_path, input_path, parent_length); + memcpy(output_path + parent_length, output_prefix, prefix_length); + memcpy(output_path + parent_length + prefix_length, base, base_length); + output_path[parent_length + prefix_length + base_length] = '\0'; + return output_path; +} + +char *path_resolve_new(const char *path) { + char *path_copy = duplicate_string(path); + char *slash; + char *parent; + char *resolved_parent; + char *resolved_path; + const char *base; + size_t length; + + if (path_copy == NULL) { + return NULL; + } + + length = strlen(path_copy); + while (length > 1 && path_copy[length - 1] == '/') { + path_copy[--length] = '\0'; + } + + slash = strrchr(path_copy, '/'); + if (slash == NULL) { + parent = duplicate_string("."); + base = path_copy; + } else if (slash == path_copy) { + parent = duplicate_string("/"); + base = slash + 1; + } else { + *slash = '\0'; + parent = duplicate_string(path_copy); + base = slash + 1; + } + + if (parent == NULL || base[0] == '\0') { + free(parent); + free(path_copy); + return NULL; + } + + resolved_parent = realpath(parent, NULL); + free(parent); + if (resolved_parent == NULL) { + free(path_copy); + return NULL; + } + + resolved_path = path_join(resolved_parent, base); + free(resolved_parent); + free(path_copy); + return resolved_path; +} + +char *path_resolve_from_file(const char *file_path, const char *relative_path) { + const char *slash; + size_t directory_length; + size_t relative_length; + size_t path_length; + size_t allocation_size; + char *path; + + if (relative_path[0] == '/') { + return duplicate_string(relative_path); + } + + slash = strrchr(file_path, '/'); + directory_length = slash == NULL ? 0 : (size_t)(slash - file_path + 1); + relative_length = strlen(relative_path); + if (checked_add(directory_length, relative_length, &path_length) != 0 || + checked_add(path_length, 1, &allocation_size) != 0) { + return NULL; + } + + path = malloc(allocation_size); + if (path == NULL) { + return NULL; + } + + memcpy(path, file_path, directory_length); + memcpy(path + directory_length, relative_path, relative_length + 1); + return path; +} + +int path_is_within(const char *parent, const char *candidate) { + size_t parent_length = strlen(parent); + + if (parent_length == 1 && parent[0] == '/') { + return candidate[0] == '/'; + } + return strncmp(parent, candidate, parent_length) == 0 && + (candidate[parent_length] == '\0' || candidate[parent_length] == '/'); +} diff --git a/src/Path/Path.h b/src/Path/Path.h new file mode 100644 index 0000000..a4ac530 --- /dev/null +++ b/src/Path/Path.h @@ -0,0 +1,10 @@ +#ifndef YAPSSG_PATH_H +#define YAPSSG_PATH_H + +char *path_join(const char *left, const char *right); +char *path_make_default_output(const char *input_path); +char *path_resolve_new(const char *path); +char *path_resolve_from_file(const char *file_path, const char *relative_path); +int path_is_within(const char *parent, const char *candidate); + +#endif |
