c_simple_http/src/static.c
Stephen Seo a467cceb90 Add separate flag for generate-static overwrite
Previous implementation allowed static files to overwrite existing files
on generate if static-dir was specified and generate-enable-overwrite
was specified. Now, an additional flag
"--generate-static-enable-overwrite" determines if static-dir files
overwrite on generate.
2024-11-25 14:17:32 +09:00

500 lines
15 KiB
C

// ISC License
//
// Copyright (c) 2024 Stephen Seo
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include "static.h"
// Standard library includes.
#include <stdio.h>
#include <stdlib.h>
// Standard C library includes.
#include <spawn.h>
// Posix includes.
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <libgen.h>
// Third party includes.
#include "SimpleArchiver/src/data_structures/linked_list.h"
#include "SimpleArchiver/src/helpers.h"
// Local includes.
#include "helpers.h"
char **environ;
void internal_fd_cleanup_helper(int *fd) {
if (fd && *fd >= 0) {
close(*fd);
*fd = -1;
}
}
void internal_cleanup_file_actions(posix_spawn_file_actions_t **actions) {
if (actions && *actions) {
posix_spawn_file_actions_destroy(*actions);
free(*actions);
*actions = NULL;
}
}
void internal_cleanup_prev_cwd(char **path) {
if (path && *path) {
int ret = chdir(*path);
if (ret != 0) {
fprintf(stderr, "WARNING chdir back to cwd failed! (errno %d)\n", errno);
}
free(*path);
*path = NULL;
}
}
int_fast8_t c_simple_http_is_xdg_mime_available(void) {
__attribute__((cleanup(internal_fd_cleanup_helper)))
int dev_null_fd = open("/dev/null", O_WRONLY);
__attribute__((cleanup(internal_cleanup_file_actions)))
posix_spawn_file_actions_t *actions =
malloc(sizeof(posix_spawn_file_actions_t));
int ret = posix_spawn_file_actions_init(actions);
if (ret != 0) {
free(actions);
actions = NULL;
return 0;
}
posix_spawn_file_actions_adddup2(actions, dev_null_fd, STDOUT_FILENO);
posix_spawn_file_actions_adddup2(actions, dev_null_fd, STDERR_FILENO);
pid_t pid;
ret = posix_spawnp(&pid,
"xdg-mime",
actions,
NULL,
(char *const[]){"xdg-mime", "--help", NULL},
environ);
if (ret != 0) {
return 0;
}
waitpid(pid, &ret, 0);
return (ret == 0 ? 1 : 0);
}
void c_simple_http_cleanup_static_file_info(
C_SIMPLE_HTTP_StaticFileInfo *file_info) {
if (file_info->buf) {
free(file_info->buf);
file_info->buf = NULL;
}
file_info->buf_size = 0;
if (file_info->mime_type) {
free(file_info->mime_type);
file_info->mime_type = NULL;
}
}
C_SIMPLE_HTTP_StaticFileInfo c_simple_http_get_file(
const char *static_dir, const char *path, int_fast8_t ignore_mime_type) {
C_SIMPLE_HTTP_StaticFileInfo file_info;
memset(&file_info, 0, sizeof(C_SIMPLE_HTTP_StaticFileInfo));
if (!static_dir || !path) {
file_info.result = STATIC_FILE_RESULT_InvalidParameter;
return file_info;
} else if (!ignore_mime_type && !c_simple_http_is_xdg_mime_available()) {
file_info.result = STATIC_FILE_RESULT_NoXDGMimeAvailable;
return file_info;
} else if (c_simple_http_static_validate_path(path) != 0) {
file_info.result = STATIC_FILE_RESULT_InvalidPath;
return file_info;
}
uint64_t buf_size = 128;
char *buf = malloc(buf_size);
char *ptr;
while (1) {
ptr = getcwd(buf, buf_size);
if (ptr == NULL) {
if (errno == ERANGE) {
buf_size *= 2;
buf = realloc(buf, buf_size);
if (buf == NULL) {
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
} else {
free(buf);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
} else {
break;
}
}
__attribute__((cleanup(internal_cleanup_prev_cwd)))
char *prev_cwd = buf;
int ret = chdir(static_dir);
if (ret != 0) {
fprintf(stderr,
"ERROR Failed to chdir into \"%s\"! (errno %d)\n",
static_dir,
errno);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
FILE *fd = NULL;
uint64_t idx = 0;
if (path[0] == '/') {
for(; path[idx] != 0; ++idx) {
if (path[idx] != '/') {
break;
}
}
if (path[idx] == 0) {
fprintf(stderr, "ERROR Received invalid path \"%s\"!\n", path);
file_info.result = STATIC_FILE_RESULT_InvalidParameter;
return file_info;
}
}
fd = fopen(path + idx, "rb");
if (fd == NULL) {
fprintf(
stderr,
"WARNING Failed to open path \"%s\" in static dir!\n",
path + idx);
file_info.result = STATIC_FILE_RESULT_404NotFound;
return file_info;
}
fseek(fd, 0, SEEK_END);
long long_ret = ftell(fd);
if (long_ret < 0) {
fprintf(stderr, "ERROR Failed to seek in path fd \"%s\"!\n", path);
file_info.result = STATIC_FILE_RESULT_FileError;
return file_info;
}
fseek(fd, 0, SEEK_SET);
file_info.buf_size = (uint64_t)long_ret;
file_info.buf = malloc(file_info.buf_size);
size_t size_t_ret = fread(file_info.buf, 1, file_info.buf_size, fd);
if (size_t_ret != file_info.buf_size) {
fprintf(stderr, "ERROR Failed to read path fd \"%s\"!\n", path);
free(file_info.buf);
file_info.buf = NULL;
file_info.buf_size = 0;
file_info.result = STATIC_FILE_RESULT_FileError;
return file_info;
}
simple_archiver_helper_cleanup_FILE(&fd);
if (ignore_mime_type) {
file_info.mime_type = strdup("application/octet-stream");
} else {
int from_xdg_mime_pipe[2];
ret = pipe(from_xdg_mime_pipe);
__attribute__((cleanup(internal_cleanup_file_actions)))
posix_spawn_file_actions_t *actions =
malloc(sizeof(posix_spawn_file_actions_t));
ret = posix_spawn_file_actions_init(actions);
if (ret != 0) {
free(actions);
actions = NULL;
c_simple_http_cleanup_static_file_info(&file_info);
close(from_xdg_mime_pipe[1]);
close(from_xdg_mime_pipe[0]);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
posix_spawn_file_actions_adddup2(actions,
from_xdg_mime_pipe[1],
STDOUT_FILENO);
// Close "read" side of pipe on "xdg-mime"'s side.
posix_spawn_file_actions_addclose(actions, from_xdg_mime_pipe[0]);
buf_size = 256;
buf = malloc(buf_size);
uint64_t buf_idx = 0;
char *path_plus_idx = (char*)path + idx;
pid_t pid;
ret = posix_spawnp(&pid,
"xdg-mime",
actions,
NULL,
(char *const[]){"xdg-mime",
"query",
"filetype",
path_plus_idx,
NULL},
environ);
if (ret != 0) {
c_simple_http_cleanup_static_file_info(&file_info);
close(from_xdg_mime_pipe[1]);
close(from_xdg_mime_pipe[0]);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
close(from_xdg_mime_pipe[1]);
ssize_t ssize_t_ret;
while (1) {
ssize_t_ret =
read(from_xdg_mime_pipe[0], buf + buf_idx, buf_size - buf_idx);
if (ssize_t_ret <= 0) {
break;
} else {
buf_idx += (uint64_t)ssize_t_ret;
if (buf_idx >= buf_size) {
buf_size *= 2;
buf = realloc(buf, buf_size);
if (buf == NULL) {
c_simple_http_cleanup_static_file_info(&file_info);
close(from_xdg_mime_pipe[0]);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
}
}
}
close(from_xdg_mime_pipe[0]);
waitpid(pid, &ret, 0);
if (ret != 0) {
c_simple_http_cleanup_static_file_info(&file_info);
file_info.result = STATIC_FILE_RESULT_InternalError;
return file_info;
}
buf[buf_idx] = 0;
if (buf[buf_idx-1] == '\n') {
buf[buf_idx-1] = 0;
}
file_info.mime_type = buf;
}
file_info.result = STATIC_FILE_RESULT_OK;
return file_info;
}
int c_simple_http_static_validate_path(const char *path) {
uint64_t length = strlen(path);
if (length >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/') {
// Starts with "..", invalid.
return 1;
}
for (uint64_t idx = 0; idx <= length && path[idx] != 0; ++idx) {
if (length - idx >= 4) {
if (path[idx] == '/'
&& path[idx + 1] == '.'
&& path[idx + 2] == '.'
&& path[idx + 3] == '/') {
// Contains "..", invalid.
return 1;
}
} else if (length - idx == 3) {
if (path[idx] == '/'
&& path[idx + 1] == '.'
&& path[idx + 2] == '.') {
// Ends with "..", invalid.
return 1;
}
}
}
return 0;
}
int c_simple_http_static_copy_over_dir(const char *from,
const char *to,
uint_fast8_t overwrite_enabled) {
__attribute__((cleanup(c_simple_http_cleanup_DIR)))
DIR *from_fd = opendir(from);
if (!from_fd) {
fprintf(stderr, "ERROR Failed to open directory \"%s\"!\n", from);
return 1;
}
const unsigned long from_len = strlen(from);
const unsigned long to_len = strlen(to);
struct dirent *dir_entry = NULL;
do {
dir_entry = readdir(from_fd);
if (!dir_entry) {
break;
} else if (strcmp(dir_entry->d_name, ".") == 0
|| strcmp(dir_entry->d_name, "..") == 0) {
continue;
} else if (dir_entry->d_type == DT_DIR) {
// Dir entry is a directory.
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *string_parts = simple_archiver_list_init();
c_simple_http_add_string_part(string_parts, from, 0);
if (from_len > 0 && from[from_len - 1] != '/') {
c_simple_http_add_string_part(string_parts, "/", 0);
}
c_simple_http_add_string_part(string_parts, dir_entry->d_name, 0);
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *combined_from = c_simple_http_combine_string_parts(string_parts);
simple_archiver_list_free(&string_parts);
string_parts = simple_archiver_list_init();
c_simple_http_add_string_part(string_parts, to, 0);
if (to_len > 0 && to[to_len - 1] != '/') {
c_simple_http_add_string_part(string_parts, "/", 0);
}
c_simple_http_add_string_part(string_parts, dir_entry->d_name, 0);
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *combined_to = c_simple_http_combine_string_parts(string_parts);
int ret = c_simple_http_static_copy_over_dir(combined_from,
combined_to,
overwrite_enabled);
if (ret != 0) {
return ret;
}
} else if (dir_entry->d_type == DT_REG) {
// Dir entry is a file.
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *string_parts = simple_archiver_list_init();
c_simple_http_add_string_part(string_parts, from, 0);
if (from_len > 0 && from[from_len - 1] != '/') {
c_simple_http_add_string_part(string_parts, "/", 0);
}
c_simple_http_add_string_part(string_parts, dir_entry->d_name, 0);
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *combined_from = c_simple_http_combine_string_parts(string_parts);
simple_archiver_list_free(&string_parts);
string_parts = simple_archiver_list_init();
c_simple_http_add_string_part(string_parts, to, 0);
if (to_len > 0 && to[to_len - 1] != '/') {
c_simple_http_add_string_part(string_parts, "/", 0);
}
c_simple_http_add_string_part(string_parts, dir_entry->d_name, 0);
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *combined_to = c_simple_http_combine_string_parts(string_parts);
if (!overwrite_enabled) {
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
FILE *fd = fopen(combined_to, "rb");
if (fd) {
fprintf(
stderr,
"WARNING \"%s\" already exists and "
"--generate-static-enable-overwrite not specified, skipping!\n",
combined_to);
continue;
}
}
__attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
char *combined_to_dup = strdup(combined_to);
char *combined_to_dirname = dirname(combined_to_dup);
int ret = c_simple_http_helper_mkdir_tree(combined_to_dirname);
if (ret != 0 && ret != 1) {
fprintf(stderr,
"ERROR Failed to create directory \"%s\"!\n",
combined_to_dirname);
return 1;
}
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
FILE *from_file_fd = fopen(combined_from, "rb");
if (!from_file_fd) {
fprintf(stderr,
"ERROR Failed to open file \"%s\" for reading!\n",
combined_from);
return 1;
}
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
FILE *to_file_fd = fopen(combined_to, "wb");
if (!to_file_fd) {
fprintf(stderr,
"ERROR Failed to open file \"%s\" for writing!\n",
combined_to);
return 1;
}
char buf[1024];
size_t fread_ret;
unsigned long fwrite_ret;
while (!feof(from_file_fd)
&& !ferror(from_file_fd)
&& !ferror(to_file_fd)) {
fread_ret = fread(buf, 1, 1024, from_file_fd);
if (fread_ret > 0) {
fwrite_ret = fwrite(buf, 1, fread_ret, to_file_fd);
if (fwrite_ret < fread_ret) {
fprintf(
stderr,
"ERROR Writing to file \"%s\" (not all bytes written)!\n",
combined_to);
return 1;
}
}
}
if (ferror(from_file_fd)) {
fprintf(stderr, "ERROR Reading from file \"%s\"!\n", combined_from);
return 1;
} else if (ferror(to_file_fd)) {
fprintf(stderr, "ERROR Writing to file \"%s\"!\n", combined_to);
return 1;
}
printf("%s -> %s\n", combined_from, combined_to);
} else {
fprintf(stderr,
"WARNING Non-dir and non-file \"%s/%s\", skipping...\n",
from,
dir_entry->d_name);
}
} while (dir_entry != NULL);
return 0;
}
// vim: et ts=2 sts=2 sw=2