472 lines
13 KiB
C
Raw Normal View History

#include "backend.h"
#include "../platform/memory.h"
#include "../platform/time.h"
#include "../http/headers.h"
#include <string.h>
struct storage_backend {
char root_path[PATH_MAX];
size_t root_len;
};
storage_backend_t *storage_create(const char *root_path) {
storage_backend_t *storage = calloc(1, sizeof(storage_backend_t));
if (!storage) return NULL;
char normalized[PATH_MAX];
path_normalize(root_path, normalized, sizeof(normalized));
strncpy(storage->root_path, normalized, sizeof(storage->root_path) - 1);
storage->root_path[sizeof(storage->root_path) - 1] = '\0';
storage->root_len = strlen(storage->root_path);
while (storage->root_len > 0 &&
(storage->root_path[storage->root_len - 1] == '/' ||
storage->root_path[storage->root_len - 1] == '\\')) {
storage->root_path[--storage->root_len] = '\0';
}
if (!file_exists(storage->root_path)) {
if (file_mkdir_recursive(storage->root_path) != NWEDAV_OK) {
free(storage);
return NULL;
}
}
return storage;
}
void storage_destroy(storage_backend_t *storage) {
free(storage);
}
const char *storage_get_root(storage_backend_t *storage) {
return storage ? storage->root_path : NULL;
}
int storage_resolve_path(storage_backend_t *storage, const char *uri, char *path, size_t path_size) {
if (!storage || !uri || !path) return NWEDAV_ERROR;
char normalized_uri[PATH_MAX];
path_normalize(uri, normalized_uri, sizeof(normalized_uri));
const char *uri_path = normalized_uri;
while (*uri_path == '/') uri_path++;
if (uri_path[0] == '\0') {
strncpy(path, storage->root_path, path_size - 1);
path[path_size - 1] = '\0';
} else {
snprintf(path, path_size, "%s%c%s", storage->root_path, PATH_SEPARATOR, uri_path);
}
for (size_t i = 0; path[i]; i++) {
if (path[i] == '/' || path[i] == '\\') {
path[i] = PATH_SEPARATOR;
}
}
if (!path_is_safe(path, storage->root_path)) {
return NWEDAV_ERROR_FORBIDDEN;
}
return NWEDAV_OK;
}
int storage_uri_from_path(storage_backend_t *storage, const char *path, char *uri, size_t uri_size) {
if (!storage || !path || !uri) return NWEDAV_ERROR;
if (strncmp(path, storage->root_path, storage->root_len) != 0) {
return NWEDAV_ERROR;
}
const char *rel = path + storage->root_len;
while (*rel == '/' || *rel == '\\') rel++;
if (*rel == '\0') {
strncpy(uri, "/", uri_size);
} else {
snprintf(uri, uri_size, "/%s", rel);
}
for (size_t i = 0; uri[i]; i++) {
if (uri[i] == '\\') uri[i] = '/';
}
return NWEDAV_OK;
}
int storage_get_info(storage_backend_t *storage, const char *path, resource_info_t *info) {
if (!storage || !path || !info) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
file_stat_t st;
if (file_stat(full_path, &st) != NWEDAV_OK) {
return NWEDAV_ERROR_NOT_FOUND;
}
memset(info, 0, sizeof(resource_info_t));
strncpy(info->path, path, sizeof(info->path) - 1);
info->path[sizeof(info->path) - 1] = '\0';
const char *name = path_basename(full_path);
if (name) {
strncpy(info->name, name, sizeof(info->name) - 1);
info->name[sizeof(info->name) - 1] = '\0';
}
info->is_collection = st.is_dir;
info->size = st.size;
info->mtime = st.mtime;
info->ctime = st.ctime;
if (st.is_dir) {
strncpy(info->content_type, "httpd/unix-directory", sizeof(info->content_type) - 1);
info->content_type[sizeof(info->content_type) - 1] = '\0';
} else {
const char *mime = http_get_mime_type_for_file(info->name);
strncpy(info->content_type, mime, sizeof(info->content_type) - 1);
info->content_type[sizeof(info->content_type) - 1] = '\0';
}
snprintf(info->etag, sizeof(info->etag), "\"%llx-%llx\"",
(unsigned long long)st.mtime, (unsigned long long)st.size);
return NWEDAV_OK;
}
int storage_exists(storage_backend_t *storage, const char *path) {
if (!storage || !path) return 0;
char full_path[PATH_MAX];
if (storage_resolve_path(storage, path, full_path, sizeof(full_path)) != NWEDAV_OK) {
return 0;
}
return file_exists(full_path);
}
int storage_is_collection(storage_backend_t *storage, const char *path) {
if (!storage || !path) return 0;
char full_path[PATH_MAX];
if (storage_resolve_path(storage, path, full_path, sizeof(full_path)) != NWEDAV_OK) {
return 0;
}
return file_is_dir(full_path);
}
int storage_read_file(storage_backend_t *storage, const char *path,
char **data, size_t *len) {
if (!storage || !path || !data || !len) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
file_stat_t st;
if (file_stat(full_path, &st) != NWEDAV_OK) {
return NWEDAV_ERROR_NOT_FOUND;
}
if (st.is_dir) {
return NWEDAV_ERROR_FORBIDDEN;
}
file_handle_t fh = file_open(full_path, FILE_READ);
#ifdef _WIN32
if (fh.handle == INVALID_HANDLE_VALUE) {
#else
if (fh.fd < 0) {
#endif
return NWEDAV_ERROR_IO;
}
*data = malloc(st.size + 1);
if (!*data) {
file_close(fh);
return NWEDAV_ERROR_MEMORY;
}
ssize_t read_bytes = file_read(fh, *data, st.size);
file_close(fh);
if (read_bytes < 0) {
free(*data);
*data = NULL;
return NWEDAV_ERROR_IO;
}
(*data)[read_bytes] = '\0';
*len = (size_t)read_bytes;
return NWEDAV_OK;
}
int storage_open_file(storage_backend_t *storage, const char *path, int *fd) {
if (!storage || !path || !fd) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
file_stat_t st;
if (file_stat(full_path, &st) != NWEDAV_OK) {
return NWEDAV_ERROR_NOT_FOUND;
}
if (st.is_dir) {
return NWEDAV_ERROR_FORBIDDEN;
}
file_handle_t fh = file_open(full_path, FILE_READ);
#ifdef _WIN32
if (fh.handle == INVALID_HANDLE_VALUE) {
return NWEDAV_ERROR_IO;
}
*fd = _open_osfhandle((intptr_t)fh.handle, 0);
#else
if (fh.fd < 0) {
return NWEDAV_ERROR_IO;
}
*fd = fh.fd;
#endif
return NWEDAV_OK;
}
int storage_write_file(storage_backend_t *storage, const char *path,
const char *data, size_t len) {
if (!storage || !path) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
char parent[PATH_MAX];
path_dirname(full_path, parent, sizeof(parent));
if (!file_exists(parent)) {
return NWEDAV_ERROR_CONFLICT;
}
file_handle_t fh = file_open(full_path, FILE_WRITE | FILE_CREATE | FILE_TRUNC);
#ifdef _WIN32
if (fh.handle == INVALID_HANDLE_VALUE) {
#else
if (fh.fd < 0) {
#endif
return NWEDAV_ERROR_IO;
}
if (data && len > 0) {
size_t written = 0;
while (written < len) {
ssize_t w = file_write(fh, data + written, len - written);
if (w < 0) {
file_close(fh);
return NWEDAV_ERROR_IO;
}
written += w;
}
}
file_close(fh);
return NWEDAV_OK;
}
int storage_delete(storage_backend_t *storage, const char *path) {
if (!storage || !path) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
if (!file_exists(full_path)) {
return NWEDAV_ERROR_NOT_FOUND;
}
return file_delete_recursive(full_path);
}
int storage_create_collection(storage_backend_t *storage, const char *path) {
if (!storage || !path) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
if (file_exists(full_path)) {
return NWEDAV_ERROR_EXISTS;
}
char parent[PATH_MAX];
path_dirname(full_path, parent, sizeof(parent));
if (!file_exists(parent)) {
return NWEDAV_ERROR_CONFLICT;
}
return file_mkdir(full_path);
}
int storage_copy(storage_backend_t *storage, const char *src, const char *dst, int overwrite) {
if (!storage || !src || !dst) return NWEDAV_ERROR;
char src_path[PATH_MAX];
char dst_path[PATH_MAX];
int ret = storage_resolve_path(storage, src, src_path, sizeof(src_path));
if (ret != NWEDAV_OK) return ret;
ret = storage_resolve_path(storage, dst, dst_path, sizeof(dst_path));
if (ret != NWEDAV_OK) return ret;
if (!file_exists(src_path)) {
return NWEDAV_ERROR_NOT_FOUND;
}
if (file_exists(dst_path) && !overwrite) {
return NWEDAV_ERROR_EXISTS;
}
char parent[PATH_MAX];
path_dirname(dst_path, parent, sizeof(parent));
if (!file_exists(parent)) {
return NWEDAV_ERROR_CONFLICT;
}
if (file_exists(dst_path)) {
file_delete_recursive(dst_path);
}
return file_copy_recursive(src_path, dst_path);
}
int storage_move(storage_backend_t *storage, const char *src, const char *dst, int overwrite) {
if (!storage || !src || !dst) return NWEDAV_ERROR;
char src_path[PATH_MAX];
char dst_path[PATH_MAX];
int ret = storage_resolve_path(storage, src, src_path, sizeof(src_path));
if (ret != NWEDAV_OK) return ret;
ret = storage_resolve_path(storage, dst, dst_path, sizeof(dst_path));
if (ret != NWEDAV_OK) return ret;
if (!file_exists(src_path)) {
return NWEDAV_ERROR_NOT_FOUND;
}
if (file_exists(dst_path) && !overwrite) {
return NWEDAV_ERROR_EXISTS;
}
char parent[PATH_MAX];
path_dirname(dst_path, parent, sizeof(parent));
if (!file_exists(parent)) {
return NWEDAV_ERROR_CONFLICT;
}
if (file_exists(dst_path)) {
file_delete_recursive(dst_path);
}
return file_rename(src_path, dst_path);
}
int storage_list(storage_backend_t *storage, const char *path, int depth,
storage_list_callback_t callback, void *user_data) {
if (!storage || !path || !callback) return NWEDAV_ERROR;
char full_path[PATH_MAX];
int ret = storage_resolve_path(storage, path, full_path, sizeof(full_path));
if (ret != NWEDAV_OK) return ret;
resource_info_t info;
ret = storage_get_info(storage, path, &info);
if (ret != NWEDAV_OK) return ret;
callback(path, &info, user_data);
if (!info.is_collection || depth == 0) {
return NWEDAV_OK;
}
dir_handle_t *dh = dir_open(full_path);
if (!dh) return NWEDAV_ERROR_IO;
dir_entry_t entry;
while (dir_read(dh, &entry) == NWEDAV_OK) {
if (strcmp(entry.name, ".") == 0 || strcmp(entry.name, "..") == 0) {
continue;
}
char child_uri[PATH_MAX];
if (strcmp(path, "/") == 0) {
snprintf(child_uri, sizeof(child_uri), "/%s", entry.name);
} else {
snprintf(child_uri, sizeof(child_uri), "%s/%s", path, entry.name);
}
resource_info_t child_info;
if (storage_get_info(storage, child_uri, &child_info) == NWEDAV_OK) {
callback(child_uri, &child_info, user_data);
if (child_info.is_collection && depth > 1) {
storage_list(storage, child_uri, depth - 1, callback, user_data);
}
}
}
dir_close(dh);
return NWEDAV_OK;
}
int64_t storage_calculate_size(storage_backend_t *storage, const char *path) {
if (!storage || !path) return 0;
char full_path[PATH_MAX];
if (storage_resolve_path(storage, path, full_path, sizeof(full_path)) != NWEDAV_OK) {
return 0;
}
file_stat_t st;
if (file_stat(full_path, &st) != NWEDAV_OK) {
return 0;
}
if (!st.is_dir) {
return st.size;
}
int64_t total = 0;
dir_handle_t *dh = dir_open(full_path);
if (!dh) return 0;
dir_entry_t entry;
while (dir_read(dh, &entry) == NWEDAV_OK) {
if (strcmp(entry.name, ".") == 0 || strcmp(entry.name, "..") == 0) {
continue;
}
char child_uri[PATH_MAX];
if (strcmp(path, "/") == 0) {
snprintf(child_uri, sizeof(child_uri), "/%s", entry.name);
} else {
snprintf(child_uri, sizeof(child_uri), "%s/%s", path, entry.name);
}
total += storage_calculate_size(storage, child_uri);
}
dir_close(dh);
return total;
}