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