chore: add initial project scaffolding with CI, build system, and core headers

This commit is contained in:
2025-11-25 20:24:12 +00:00
commit 16db22b3de
49 changed files with 9193 additions and 0 deletions
+347
View File
@@ -0,0 +1,347 @@
#include "auth.h"
#include "../platform/pal.h"
#include "../platform/memory.h"
#include "../user/usermgr.h"
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <wincrypt.h>
#else
#include <openssl/md5.h>
#include <openssl/rand.h>
#endif
struct auth_context {
user_manager_t *usermgr;
auth_method_t method;
char realm[128];
char current_nonce[64];
time_t nonce_time;
mutex_t mutex;
};
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char *auth_base64_encode(const unsigned char *data, size_t len) {
size_t out_len = 4 * ((len + 2) / 3);
char *out = malloc(out_len + 1);
if (!out) return NULL;
size_t i, j;
for (i = 0, j = 0; i < len;) {
uint32_t octet_a = i < len ? data[i++] : 0;
uint32_t octet_b = i < len ? data[i++] : 0;
uint32_t octet_c = i < len ? data[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
out[j++] = base64_chars[(triple >> 18) & 0x3F];
out[j++] = base64_chars[(triple >> 12) & 0x3F];
out[j++] = base64_chars[(triple >> 6) & 0x3F];
out[j++] = base64_chars[triple & 0x3F];
}
int mod = len % 3;
if (mod > 0) {
for (int k = 0; k < 3 - mod; k++) {
out[out_len - 1 - k] = '=';
}
}
out[out_len] = '\0';
return out;
}
unsigned char *auth_base64_decode(const char *data, size_t *out_len) {
size_t len = strlen(data);
if (len % 4 != 0) return NULL;
*out_len = len / 4 * 3;
if (data[len - 1] == '=') (*out_len)--;
if (data[len - 2] == '=') (*out_len)--;
unsigned char *out = malloc(*out_len);
if (!out) return NULL;
static const unsigned char d[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64
};
size_t i, j;
for (i = 0, j = 0; i < len;) {
uint32_t a = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t b = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t c = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t e = data[i] == '=' ? 0 : d[(unsigned char)data[i]]; i++;
uint32_t triple = (a << 18) + (b << 12) + (c << 6) + e;
if (j < *out_len) out[j++] = (triple >> 16) & 0xFF;
if (j < *out_len) out[j++] = (triple >> 8) & 0xFF;
if (j < *out_len) out[j++] = triple & 0xFF;
}
return out;
}
auth_context_t *auth_create(const char *db_path, auth_method_t method, const char *realm) {
auth_context_t *auth = calloc(1, sizeof(auth_context_t));
if (!auth) return NULL;
auth->method = method;
strncpy(auth->realm, realm ? realm : "WebDAV", sizeof(auth->realm) - 1);
if (pal_mutex_init(&auth->mutex) != NWEDAV_OK) {
free(auth);
return NULL;
}
auth->usermgr = user_manager_create(db_path);
if (!auth->usermgr) {
pal_mutex_destroy(&auth->mutex);
free(auth);
return NULL;
}
return auth;
}
void auth_destroy(auth_context_t *auth) {
if (!auth) return;
user_manager_destroy(auth->usermgr);
pal_mutex_destroy(&auth->mutex);
free(auth);
}
int auth_parse_basic(const char *header, char *username, size_t user_size,
char *password, size_t pass_size) {
if (!header || !username || !password) return NWEDAV_ERROR;
while (*header == ' ') header++;
if (str_ncasecmp(header, "Basic ", 6) != 0) return NWEDAV_ERROR;
header += 6;
while (*header == ' ') header++;
size_t decoded_len;
unsigned char *decoded = auth_base64_decode(header, &decoded_len);
if (!decoded) return NWEDAV_ERROR;
char *colon = memchr(decoded, ':', decoded_len);
if (!colon) {
free(decoded);
return NWEDAV_ERROR;
}
size_t user_len = colon - (char *)decoded;
size_t pass_len = decoded_len - user_len - 1;
if (user_len >= user_size || pass_len >= pass_size) {
free(decoded);
return NWEDAV_ERROR;
}
memcpy(username, decoded, user_len);
username[user_len] = '\0';
memcpy(password, colon + 1, pass_len);
password[pass_len] = '\0';
free(decoded);
return NWEDAV_OK;
}
static char *extract_digest_param(const char *header, const char *param) {
char search[64];
snprintf(search, sizeof(search), "%s=", param);
const char *start = strstr(header, search);
if (!start) return NULL;
start += strlen(search);
while (*start == ' ') start++;
if (*start == '"') {
start++;
const char *end = strchr(start, '"');
if (!end) return NULL;
return str_ndup(start, end - start);
} else {
const char *end = start;
while (*end && *end != ',' && *end != ' ') end++;
return str_ndup(start, end - start);
}
}
int auth_parse_digest(const char *header, char *username, char *realm,
char *nonce, char *uri, char *nc, char *cnonce,
char *qop, char *response) {
if (!header) return NWEDAV_ERROR;
while (*header == ' ') header++;
if (str_ncasecmp(header, "Digest ", 7) != 0) return NWEDAV_ERROR;
header += 7;
char *tmp;
tmp = extract_digest_param(header, "username");
if (tmp) { strncpy(username, tmp, 63); free(tmp); }
tmp = extract_digest_param(header, "realm");
if (tmp) { strncpy(realm, tmp, 127); free(tmp); }
tmp = extract_digest_param(header, "nonce");
if (tmp) { strncpy(nonce, tmp, 63); free(tmp); }
tmp = extract_digest_param(header, "uri");
if (tmp) { strncpy(uri, tmp, 255); free(tmp); }
tmp = extract_digest_param(header, "nc");
if (tmp) { strncpy(nc, tmp, 15); free(tmp); }
tmp = extract_digest_param(header, "cnonce");
if (tmp) { strncpy(cnonce, tmp, 63); free(tmp); }
tmp = extract_digest_param(header, "qop");
if (tmp) { strncpy(qop, tmp, 15); free(tmp); }
tmp = extract_digest_param(header, "response");
if (tmp) { strncpy(response, tmp, 63); free(tmp); }
return NWEDAV_OK;
}
static void generate_nonce(char *nonce, size_t size) {
unsigned char rand_bytes[16];
#ifdef _WIN32
HCRYPTPROV hProv;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
CryptGenRandom(hProv, sizeof(rand_bytes), rand_bytes);
CryptReleaseContext(hProv, 0);
}
#else
RAND_bytes(rand_bytes, sizeof(rand_bytes));
#endif
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < 16 && (size_t)(i * 2 + 1) < size - 1; i++) {
nonce[i * 2] = hex[rand_bytes[i] >> 4];
nonce[i * 2 + 1] = hex[rand_bytes[i] & 0x0f];
}
nonce[32] = '\0';
}
int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user) {
if (!auth || !req || !user) return NWEDAV_ERROR;
if (auth->method == AUTH_METHOD_NONE) {
memset(user, 0, sizeof(auth_user_t));
strcpy(user->username, "anonymous");
return NWEDAV_OK;
}
if (!req->authorization) {
return NWEDAV_ERROR_AUTH;
}
if (auth->method == AUTH_METHOD_BASIC) {
char username[64] = {0};
char password[256] = {0};
if (auth_parse_basic(req->authorization, username, sizeof(username),
password, sizeof(password)) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user_t db_user;
if (user_manager_authenticate(auth->usermgr, username, password, &db_user) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user->id = db_user.id;
strncpy(user->username, db_user.username, sizeof(user->username) - 1);
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
return NWEDAV_OK;
}
if (auth->method == AUTH_METHOD_DIGEST) {
char username[64] = {0};
char realm[128] = {0};
char nonce[64] = {0};
char uri[256] = {0};
char nc[16] = {0};
char cnonce[64] = {0};
char qop[16] = {0};
char response[64] = {0};
if (auth_parse_digest(req->authorization, username, realm, nonce, uri,
nc, cnonce, qop, response) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user_t db_user;
if (user_manager_get_by_username(auth->usermgr, username, &db_user) != NWEDAV_OK) {
return NWEDAV_ERROR_AUTH;
}
user->id = db_user.id;
strncpy(user->username, db_user.username, sizeof(user->username) - 1);
strncpy(user->email, db_user.email, sizeof(user->email) - 1);
strncpy(user->display_name, db_user.display_name, sizeof(user->display_name) - 1);
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
return NWEDAV_OK;
}
return NWEDAV_ERROR_AUTH;
}
int auth_build_challenge(auth_context_t *auth, http_response_t *res) {
if (!auth || !res) return NWEDAV_ERROR;
char header[512];
if (auth->method == AUTH_METHOD_BASIC) {
snprintf(header, sizeof(header), "Basic realm=\"%s\"", auth->realm);
} else if (auth->method == AUTH_METHOD_DIGEST) {
pal_mutex_lock(&auth->mutex);
time_t now = time(NULL);
if (now - auth->nonce_time > 300 || auth->current_nonce[0] == '\0') {
generate_nonce(auth->current_nonce, sizeof(auth->current_nonce));
auth->nonce_time = now;
}
snprintf(header, sizeof(header),
"Digest realm=\"%s\", nonce=\"%s\", qop=\"auth\", algorithm=MD5",
auth->realm, auth->current_nonce);
pal_mutex_unlock(&auth->mutex);
} else {
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_401_UNAUTHORIZED);
http_response_add_header(res, "WWW-Authenticate", header);
http_response_error(res, HTTP_401_UNAUTHORIZED, "Authentication required");
return NWEDAV_OK;
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef AUTH_H
#define AUTH_H
#include "nwedav.h"
#include "../http/parser.h"
#include "../http/response.h"
typedef struct {
uint32_t id;
char username[64];
char email[256];
char display_name[256];
char home_directory[PATH_MAX];
uint64_t quota_bytes;
uint64_t used_bytes;
int enabled;
} auth_user_t;
typedef struct auth_context auth_context_t;
auth_context_t *auth_create(const char *db_path, auth_method_t method, const char *realm);
void auth_destroy(auth_context_t *auth);
int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user);
int auth_build_challenge(auth_context_t *auth, http_response_t *res);
int auth_parse_basic(const char *header, char *username, size_t user_size,
char *password, size_t pass_size);
int auth_parse_digest(const char *header, char *username, char *realm,
char *nonce, char *uri, char *nc, char *cnonce,
char *qop, char *response);
char *auth_base64_encode(const unsigned char *data, size_t len);
unsigned char *auth_base64_decode(const char *data, size_t *out_len);
#endif
+501
View File
@@ -0,0 +1,501 @@
#include "../platform/pal.h"
#include "../platform/memory.h"
#include "../user/usermgr.h"
#include "../core/config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#include <conio.h>
#else
#include <termios.h>
#endif
static char g_db_path[PATH_MAX] = "./nwedav.db";
static int g_verbose = 0;
static int g_json = 0;
static void print_usage(void) {
printf("Usage: webdavctl [options] <command> [arguments]\n\n");
printf("Options:\n");
printf(" -d, --database <path> Database file path (default: ./nwedav.db)\n");
printf(" -v, --verbose Verbose output\n");
printf(" --json Output in JSON format\n");
printf(" -h, --help Show this help\n\n");
printf("Commands:\n");
printf(" user list List all users\n");
printf(" user add <username> Add a new user\n");
printf(" user delete <username> Delete a user\n");
printf(" user enable <username> Enable a user\n");
printf(" user disable <username> Disable a user\n");
printf(" user passwd <username> Change user password\n");
printf(" user info <username> Show user information\n");
printf(" quota show <username> Show user quota\n");
printf(" quota set <username> <bytes> Set user quota\n");
printf(" group add <name> Add a group\n");
printf(" group delete <name> Delete a group\n");
printf(" group adduser <group> <user> Add user to group\n");
printf(" group removeuser <group> <user> Remove user from group\n");
printf(" init Initialize database\n");
}
static char *read_password(const char *prompt) {
printf("%s", prompt);
fflush(stdout);
static char password[256];
int i = 0;
#ifdef _WIN32
int ch;
while ((ch = _getch()) != '\r' && i < 255) {
if (ch == '\b' && i > 0) {
i--;
printf("\b \b");
} else if (ch >= 32) {
password[i++] = ch;
printf("*");
}
}
printf("\n");
#else
struct termios old, new;
tcgetattr(0, &old);
new = old;
new.c_lflag &= ~ECHO;
tcsetattr(0, TCSANOW, &new);
if (fgets(password, sizeof(password), stdin)) {
size_t len = strlen(password);
if (len > 0 && password[len - 1] == '\n') {
password[len - 1] = '\0';
i = len - 1;
} else {
i = len;
}
}
tcsetattr(0, TCSANOW, &old);
printf("\n");
#endif
password[i] = '\0';
return password;
}
static int user_list_callback(user_t *user, void *data) {
(void)data;
if (g_json) {
printf(" {\"id\": %u, \"username\": \"%s\", \"email\": \"%s\", "
"\"enabled\": %s, \"quota\": %llu, \"used\": %llu}",
user->id, user->username, user->email,
user->enabled ? "true" : "false",
(unsigned long long)user->quota_bytes,
(unsigned long long)user->used_bytes);
} else {
printf("%-4u %-20s %-30s %-8s %12llu %12llu\n",
user->id, user->username, user->email,
user->enabled ? "enabled" : "disabled",
(unsigned long long)user->quota_bytes,
(unsigned long long)user->used_bytes);
}
return 0;
}
static int cmd_user_list(user_manager_t *mgr) {
if (g_json) {
printf("[\n");
} else {
printf("%-4s %-20s %-30s %-8s %12s %12s\n",
"ID", "Username", "Email", "Status", "Quota", "Used");
printf("----------------------------------------------------------------------------\n");
}
user_manager_list(mgr, user_list_callback, NULL);
if (g_json) {
printf("\n]\n");
}
return 0;
}
static int cmd_user_add(user_manager_t *mgr, const char *username) {
char *password = read_password("Password: ");
char *confirm = read_password("Confirm password: ");
if (strcmp(password, confirm) != 0) {
fprintf(stderr, "Passwords do not match\n");
return 1;
}
printf("Email (optional): ");
char email[256] = {0};
if (fgets(email, sizeof(email), stdin)) {
size_t len = strlen(email);
if (len > 0 && email[len - 1] == '\n') {
email[len - 1] = '\0';
}
}
printf("Quota in bytes (0 for unlimited): ");
char quota_str[64] = {0};
uint64_t quota = 0;
if (fgets(quota_str, sizeof(quota_str), stdin)) {
quota = strtoull(quota_str, NULL, 10);
}
int ret = user_manager_add(mgr, username, password, email[0] ? email : NULL, quota);
if (ret == NWEDAV_OK) {
printf("User '%s' created successfully\n", username);
return 0;
} else {
fprintf(stderr, "Failed to create user '%s'\n", username);
return 1;
}
}
static int cmd_user_delete(user_manager_t *mgr, const char *username) {
printf("Are you sure you want to delete user '%s'? (yes/no): ", username);
char confirm[16] = {0};
if (fgets(confirm, sizeof(confirm), stdin)) {
if (strncmp(confirm, "yes", 3) != 0) {
printf("Aborted\n");
return 0;
}
}
int ret = user_manager_delete(mgr, username);
if (ret == NWEDAV_OK) {
printf("User '%s' deleted\n", username);
return 0;
} else {
fprintf(stderr, "Failed to delete user '%s'\n", username);
return 1;
}
}
static int cmd_user_enable(user_manager_t *mgr, const char *username, int enable) {
int ret = user_manager_enable(mgr, username, enable);
if (ret == NWEDAV_OK) {
printf("User '%s' %s\n", username, enable ? "enabled" : "disabled");
return 0;
} else {
fprintf(stderr, "Failed to %s user '%s'\n", enable ? "enable" : "disable", username);
return 1;
}
}
static int cmd_user_passwd(user_manager_t *mgr, const char *username) {
char *password = read_password("New password: ");
char *confirm = read_password("Confirm password: ");
if (strcmp(password, confirm) != 0) {
fprintf(stderr, "Passwords do not match\n");
return 1;
}
int ret = user_manager_set_password(mgr, username, password);
if (ret == NWEDAV_OK) {
printf("Password updated for user '%s'\n", username);
return 0;
} else {
fprintf(stderr, "Failed to update password for user '%s'\n", username);
return 1;
}
}
static int cmd_user_info(user_manager_t *mgr, const char *username) {
user_t user;
int ret = user_manager_get_by_username(mgr, username, &user);
if (ret != NWEDAV_OK) {
fprintf(stderr, "User '%s' not found\n", username);
return 1;
}
if (g_json) {
printf("{\n");
printf(" \"id\": %u,\n", user.id);
printf(" \"username\": \"%s\",\n", user.username);
printf(" \"email\": \"%s\",\n", user.email);
printf(" \"display_name\": \"%s\",\n", user.display_name);
printf(" \"home_directory\": \"%s\",\n", user.home_directory);
printf(" \"quota_bytes\": %llu,\n", (unsigned long long)user.quota_bytes);
printf(" \"used_bytes\": %llu,\n", (unsigned long long)user.used_bytes);
printf(" \"enabled\": %s,\n", user.enabled ? "true" : "false");
printf(" \"created_at\": %lld,\n", (long long)user.created_at);
printf(" \"last_login\": %lld\n", (long long)user.last_login);
printf("}\n");
} else {
printf("User Information:\n");
printf(" ID: %u\n", user.id);
printf(" Username: %s\n", user.username);
printf(" Email: %s\n", user.email);
printf(" Display Name: %s\n", user.display_name);
printf(" Home Directory: %s\n", user.home_directory);
printf(" Quota: %llu bytes\n", (unsigned long long)user.quota_bytes);
printf(" Used: %llu bytes\n", (unsigned long long)user.used_bytes);
printf(" Status: %s\n", user.enabled ? "enabled" : "disabled");
}
return 0;
}
static int cmd_quota_show(user_manager_t *mgr, const char *username) {
user_t user;
int ret = user_manager_get_by_username(mgr, username, &user);
if (ret != NWEDAV_OK) {
fprintf(stderr, "User '%s' not found\n", username);
return 1;
}
double quota_gb = user.quota_bytes / (1024.0 * 1024.0 * 1024.0);
double used_gb = user.used_bytes / (1024.0 * 1024.0 * 1024.0);
double percent = user.quota_bytes > 0 ? (100.0 * user.used_bytes / user.quota_bytes) : 0;
printf("Quota for user '%s':\n", username);
printf(" Quota: %.2f GB (%llu bytes)\n", quota_gb, (unsigned long long)user.quota_bytes);
printf(" Used: %.2f GB (%llu bytes)\n", used_gb, (unsigned long long)user.used_bytes);
printf(" Usage: %.1f%%\n", percent);
return 0;
}
static int cmd_quota_set(user_manager_t *mgr, const char *username, const char *quota_str) {
user_t user;
int ret = user_manager_get_by_username(mgr, username, &user);
if (ret != NWEDAV_OK) {
fprintf(stderr, "User '%s' not found\n", username);
return 1;
}
uint64_t quota = strtoull(quota_str, NULL, 10);
ret = user_manager_set_quota(mgr, user.id, quota);
if (ret == NWEDAV_OK) {
printf("Quota set to %llu bytes for user '%s'\n", (unsigned long long)quota, username);
return 0;
} else {
fprintf(stderr, "Failed to set quota\n");
return 1;
}
}
static int cmd_group_add(user_manager_t *mgr, const char *name) {
int ret = user_manager_group_add(mgr, name, NULL);
if (ret == NWEDAV_OK) {
printf("Group '%s' created\n", name);
return 0;
} else {
fprintf(stderr, "Failed to create group '%s'\n", name);
return 1;
}
}
static int cmd_group_delete(user_manager_t *mgr, const char *name) {
int ret = user_manager_group_delete(mgr, name);
if (ret == NWEDAV_OK) {
printf("Group '%s' deleted\n", name);
return 0;
} else {
fprintf(stderr, "Failed to delete group '%s'\n", name);
return 1;
}
}
static int cmd_group_adduser(user_manager_t *mgr, const char *group, const char *user) {
int ret = user_manager_group_add_user(mgr, group, user);
if (ret == NWEDAV_OK) {
printf("User '%s' added to group '%s'\n", user, group);
return 0;
} else {
fprintf(stderr, "Failed to add user '%s' to group '%s'\n", user, group);
return 1;
}
}
static int cmd_group_removeuser(user_manager_t *mgr, const char *group, const char *user) {
int ret = user_manager_group_remove_user(mgr, group, user);
if (ret == NWEDAV_OK) {
printf("User '%s' removed from group '%s'\n", user, group);
return 0;
} else {
fprintf(stderr, "Failed to remove user '%s' from group '%s'\n", user, group);
return 1;
}
}
static int cmd_init(void) {
user_manager_t *mgr = user_manager_create(g_db_path);
if (!mgr) {
fprintf(stderr, "Failed to initialize database at '%s'\n", g_db_path);
return 1;
}
printf("Database initialized at '%s'\n", g_db_path);
if (user_manager_count(mgr) == 0) {
printf("Creating default admin user...\n");
int ret = user_manager_add(mgr, "admin", "admin", "admin@localhost", 0);
if (ret == NWEDAV_OK) {
printf("Default user 'admin' created with password 'admin'\n");
printf("IMPORTANT: Change the admin password immediately!\n");
}
}
user_manager_destroy(mgr);
return 0;
}
int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage();
return 0;
} else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--database") == 0) {
if (i + 1 < argc) {
strncpy(g_db_path, argv[++i], sizeof(g_db_path) - 1);
}
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
g_verbose = 1;
} else if (strcmp(argv[i], "--json") == 0) {
g_json = 1;
} else if (argv[i][0] != '-') {
break;
}
}
if (i >= argc) {
print_usage();
return 1;
}
const char *command = argv[i++];
if (strcmp(command, "init") == 0) {
return cmd_init();
}
user_manager_t *mgr = user_manager_create(g_db_path);
if (!mgr) {
fprintf(stderr, "Failed to open database '%s'\n", g_db_path);
fprintf(stderr, "Run 'webdavctl init' first to initialize the database\n");
return 1;
}
int ret = 1;
if (strcmp(command, "user") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing user subcommand\n");
} else {
const char *subcommand = argv[i++];
if (strcmp(subcommand, "list") == 0) {
ret = cmd_user_list(mgr);
} else if (strcmp(subcommand, "add") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_add(mgr, argv[i]);
}
} else if (strcmp(subcommand, "delete") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_delete(mgr, argv[i]);
}
} else if (strcmp(subcommand, "enable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_enable(mgr, argv[i], 1);
}
} else if (strcmp(subcommand, "disable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_enable(mgr, argv[i], 0);
}
} else if (strcmp(subcommand, "passwd") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_passwd(mgr, argv[i]);
}
} else if (strcmp(subcommand, "info") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_info(mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown user subcommand: %s\n", subcommand);
}
}
} else if (strcmp(command, "quota") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing quota subcommand\n");
} else {
const char *subcommand = argv[i++];
if (strcmp(subcommand, "show") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_quota_show(mgr, argv[i]);
}
} else if (strcmp(subcommand, "set") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or quota value\n");
} else {
ret = cmd_quota_set(mgr, argv[i], argv[i + 1]);
}
} else {
fprintf(stderr, "Unknown quota subcommand: %s\n", subcommand);
}
}
} else if (strcmp(command, "group") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing group subcommand\n");
} else {
const char *subcommand = argv[i++];
if (strcmp(subcommand, "add") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing group name\n");
} else {
ret = cmd_group_add(mgr, argv[i]);
}
} else if (strcmp(subcommand, "delete") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing group name\n");
} else {
ret = cmd_group_delete(mgr, argv[i]);
}
} else if (strcmp(subcommand, "adduser") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing group name or username\n");
} else {
ret = cmd_group_adduser(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "removeuser") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing group name or username\n");
} else {
ret = cmd_group_removeuser(mgr, argv[i], argv[i + 1]);
}
} else {
fprintf(stderr, "Unknown group subcommand: %s\n", subcommand);
}
}
} else {
fprintf(stderr, "Unknown command: %s\n", command);
print_usage();
}
user_manager_destroy(mgr);
return ret;
}
+222
View File
@@ -0,0 +1,222 @@
#include "connpool.h"
#include <string.h>
struct connection_pool {
connection_t *free_list;
size_t free_count;
size_t active_count;
size_t max_connections;
mutex_t mutex;
};
connection_pool_t *connpool_create(size_t max_connections) {
connection_pool_t *pool = calloc(1, sizeof(connection_pool_t));
if (!pool) return NULL;
pool->max_connections = max_connections;
if (pal_mutex_init(&pool->mutex) != NWEDAV_OK) {
free(pool);
return NULL;
}
size_t prealloc = max_connections < 1000 ? max_connections : 1000;
for (size_t i = 0; i < prealloc; i++) {
connection_t *conn = calloc(1, sizeof(connection_t));
if (!conn) break;
conn->read_buf = buffer_create(NWEDAV_DEFAULT_BUFFER_SIZE);
conn->write_buf = buffer_create(NWEDAV_DEFAULT_BUFFER_SIZE);
if (!conn->read_buf || !conn->write_buf) {
buffer_destroy(conn->read_buf);
buffer_destroy(conn->write_buf);
free(conn);
break;
}
if (pal_mutex_init(&conn->mutex) != NWEDAV_OK) {
buffer_destroy(conn->read_buf);
buffer_destroy(conn->write_buf);
free(conn);
break;
}
conn->next = pool->free_list;
pool->free_list = conn;
pool->free_count++;
}
return pool;
}
void connpool_destroy(connection_pool_t *pool) {
if (!pool) return;
pal_mutex_lock(&pool->mutex);
connection_t *conn = pool->free_list;
while (conn) {
connection_t *next = conn->next;
buffer_destroy(conn->read_buf);
buffer_destroy(conn->write_buf);
http_request_free(&conn->request);
http_response_free(&conn->response);
pal_mutex_destroy(&conn->mutex);
free(conn);
conn = next;
}
pal_mutex_unlock(&pool->mutex);
pal_mutex_destroy(&pool->mutex);
free(pool);
}
connection_t *connpool_acquire(connection_pool_t *pool) {
if (!pool) return NULL;
pal_mutex_lock(&pool->mutex);
if (pool->active_count >= pool->max_connections) {
pal_mutex_unlock(&pool->mutex);
return NULL;
}
connection_t *conn;
if (pool->free_list) {
conn = pool->free_list;
pool->free_list = conn->next;
pool->free_count--;
} else {
conn = calloc(1, sizeof(connection_t));
if (!conn) {
pal_mutex_unlock(&pool->mutex);
return NULL;
}
conn->read_buf = buffer_create(NWEDAV_DEFAULT_BUFFER_SIZE);
conn->write_buf = buffer_create(NWEDAV_DEFAULT_BUFFER_SIZE);
if (!conn->read_buf || !conn->write_buf) {
buffer_destroy(conn->read_buf);
buffer_destroy(conn->write_buf);
free(conn);
pal_mutex_unlock(&pool->mutex);
return NULL;
}
if (pal_mutex_init(&conn->mutex) != NWEDAV_OK) {
buffer_destroy(conn->read_buf);
buffer_destroy(conn->write_buf);
free(conn);
pal_mutex_unlock(&pool->mutex);
return NULL;
}
}
pool->active_count++;
conn->next = NULL;
conn->prev = NULL;
pal_mutex_unlock(&pool->mutex);
return conn;
}
void connpool_release(connection_pool_t *pool, connection_t *conn) {
if (!pool || !conn) return;
connection_reset(conn);
pal_mutex_lock(&pool->mutex);
conn->next = pool->free_list;
pool->free_list = conn;
pool->free_count++;
pool->active_count--;
pal_mutex_unlock(&pool->mutex);
}
void connection_init(connection_t *conn, socket_t fd, struct sockaddr *addr, socklen_t addr_len) {
pal_mutex_lock(&conn->mutex);
conn->fd = fd;
conn->state = CONN_STATE_READING_REQUEST;
conn->last_activity = pal_time_monotonic();
conn->keep_alive = 1;
conn->requests_served = 0;
conn->processing = 0;
if (addr && addr_len > 0) {
memcpy(&conn->addr, addr, addr_len);
conn->addr_len = addr_len;
if (addr->sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
inet_ntop(AF_INET, &sin->sin_addr, conn->client_ip, sizeof(conn->client_ip));
} else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, conn->client_ip, sizeof(conn->client_ip));
}
}
buffer_reset(conn->read_buf);
buffer_reset(conn->write_buf);
conn->write_offset = 0;
http_request_init(&conn->request);
http_response_init(&conn->response);
pal_mutex_unlock(&conn->mutex);
}
void connection_reset(connection_t *conn) {
if (!conn) return;
http_request_free(&conn->request);
http_response_free(&conn->response);
http_request_init(&conn->request);
http_response_init(&conn->response);
buffer_reset(conn->read_buf);
buffer_reset(conn->write_buf);
conn->write_offset = 0;
conn->state = CONN_STATE_READING_REQUEST;
conn->last_activity = pal_time_monotonic();
conn->processing = 0;
}
void connection_close(connection_t *conn) {
if (!conn) return;
if (conn->fd != SOCKET_INVALID) {
pal_socket_shutdown(conn->fd, 2);
pal_socket_close(conn->fd);
conn->fd = SOCKET_INVALID;
}
conn->state = CONN_STATE_CLOSING;
}
size_t connpool_active_count(connection_pool_t *pool) {
if (!pool) return 0;
pal_mutex_lock(&pool->mutex);
size_t count = pool->active_count;
pal_mutex_unlock(&pool->mutex);
return count;
}
size_t connpool_free_count(connection_pool_t *pool) {
if (!pool) return 0;
pal_mutex_lock(&pool->mutex);
size_t count = pool->free_count;
pal_mutex_unlock(&pool->mutex);
return count;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef CONNPOOL_H
#define CONNPOOL_H
#include "nwedav.h"
#include "../platform/pal.h"
#include "../platform/memory.h"
#include "../http/parser.h"
#include "../http/response.h"
typedef struct connection {
socket_t fd;
conn_state_t state;
struct sockaddr_storage addr;
socklen_t addr_len;
buffer_t *read_buf;
buffer_t *write_buf;
size_t write_offset;
http_request_t request;
http_response_t response;
void *tls_ctx;
int64_t last_activity;
int keep_alive;
int requests_served;
uint32_t user_id;
char username[64];
char client_ip[64];
mutex_t mutex;
volatile int processing;
struct connection *next;
struct connection *prev;
} connection_t;
typedef struct connection_pool connection_pool_t;
connection_pool_t *connpool_create(size_t max_connections);
void connpool_destroy(connection_pool_t *pool);
connection_t *connpool_acquire(connection_pool_t *pool);
void connpool_release(connection_pool_t *pool, connection_t *conn);
void connection_init(connection_t *conn, socket_t fd, struct sockaddr *addr, socklen_t addr_len);
void connection_reset(connection_t *conn);
void connection_close(connection_t *conn);
size_t connpool_active_count(connection_pool_t *pool);
size_t connpool_free_count(connection_pool_t *pool);
#endif
+509
View File
@@ -0,0 +1,509 @@
#include "eventloop.h"
#include <string.h>
#ifdef __linux__
#include <sys/epoll.h>
#define USE_EPOLL
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/event.h>
#define USE_KQUEUE
#elif defined(_WIN32)
#define USE_SELECT
#else
#include <poll.h>
#define USE_POLL
#endif
struct event_loop {
#if defined(USE_EPOLL)
int epoll_fd;
#elif defined(USE_KQUEUE)
int kqueue_fd;
#elif defined(USE_SELECT)
fd_set read_set;
fd_set write_set;
fd_set error_set;
socket_t max_fd;
struct {
socket_t fd;
event_flags_t events;
void *data;
} *fds;
int fd_count;
int fd_capacity;
mutex_t mutex;
#else
struct pollfd *pollfds;
void **userdata;
int count;
int capacity;
mutex_t mutex;
#endif
int max_events;
};
#if defined(USE_EPOLL)
event_loop_t *event_loop_create(int max_events) {
event_loop_t *loop = calloc(1, sizeof(event_loop_t));
if (!loop) return NULL;
loop->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (loop->epoll_fd < 0) {
free(loop);
return NULL;
}
loop->max_events = max_events;
return loop;
}
void event_loop_destroy(event_loop_t *loop) {
if (!loop) return;
close(loop->epoll_fd);
free(loop);
}
int event_loop_add(event_loop_t *loop, socket_t fd, event_flags_t events, void *data) {
struct epoll_event ev;
ev.events = 0;
if (events & EVENT_READ) ev.events |= EPOLLIN;
if (events & EVENT_WRITE) ev.events |= EPOLLOUT;
ev.data.ptr = data;
if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
int event_loop_modify(event_loop_t *loop, socket_t fd, event_flags_t events) {
struct epoll_event ev;
ev.events = 0;
if (events & EVENT_READ) ev.events |= EPOLLIN;
if (events & EVENT_WRITE) ev.events |= EPOLLOUT;
ev.data.fd = fd;
if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_MOD, fd, &ev) < 0) {
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
int event_loop_remove(event_loop_t *loop, socket_t fd) {
if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) {
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
int event_loop_wait(event_loop_t *loop, event_t *events, int max, int timeout_ms) {
struct epoll_event *ep_events = alloca(max * sizeof(struct epoll_event));
int n = epoll_wait(loop->epoll_fd, ep_events, max, timeout_ms);
if (n < 0) return -1;
for (int i = 0; i < n; i++) {
events[i].fd = ep_events[i].data.fd;
events[i].user_data = ep_events[i].data.ptr;
events[i].events = 0;
if (ep_events[i].events & EPOLLIN) events[i].events |= EVENT_READ;
if (ep_events[i].events & EPOLLOUT) events[i].events |= EVENT_WRITE;
if (ep_events[i].events & EPOLLERR) events[i].events |= EVENT_ERROR;
if (ep_events[i].events & EPOLLHUP) events[i].events |= EVENT_HUP;
}
return n;
}
#elif defined(USE_KQUEUE)
event_loop_t *event_loop_create(int max_events) {
event_loop_t *loop = calloc(1, sizeof(event_loop_t));
if (!loop) return NULL;
loop->kqueue_fd = kqueue();
if (loop->kqueue_fd < 0) {
free(loop);
return NULL;
}
loop->max_events = max_events;
return loop;
}
void event_loop_destroy(event_loop_t *loop) {
if (!loop) return;
close(loop->kqueue_fd);
free(loop);
}
int event_loop_add(event_loop_t *loop, socket_t fd, event_flags_t events, void *data) {
struct kevent ev[2];
int n = 0;
if (events & EVENT_READ) {
EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, data);
}
if (events & EVENT_WRITE) {
EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, data);
}
if (kevent(loop->kqueue_fd, ev, n, NULL, 0, NULL) < 0) {
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
int event_loop_modify(event_loop_t *loop, socket_t fd, event_flags_t events) {
struct kevent ev[4];
int n = 0;
if (events & EVENT_READ) {
EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
} else {
EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
}
if (events & EVENT_WRITE) {
EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL);
} else {
EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
}
kevent(loop->kqueue_fd, ev, n, NULL, 0, NULL);
return NWEDAV_OK;
}
int event_loop_remove(event_loop_t *loop, socket_t fd) {
struct kevent ev[2];
EV_SET(&ev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
EV_SET(&ev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
kevent(loop->kqueue_fd, ev, 2, NULL, 0, NULL);
return NWEDAV_OK;
}
int event_loop_wait(event_loop_t *loop, event_t *events, int max, int timeout_ms) {
struct kevent *kev = alloca(max * sizeof(struct kevent));
struct timespec ts, *pts = NULL;
if (timeout_ms >= 0) {
ts.tv_sec = timeout_ms / 1000;
ts.tv_nsec = (timeout_ms % 1000) * 1000000;
pts = &ts;
}
int n = kevent(loop->kqueue_fd, NULL, 0, kev, max, pts);
if (n < 0) return -1;
for (int i = 0; i < n; i++) {
events[i].fd = (socket_t)kev[i].ident;
events[i].user_data = kev[i].udata;
events[i].events = 0;
if (kev[i].filter == EVFILT_READ) events[i].events |= EVENT_READ;
if (kev[i].filter == EVFILT_WRITE) events[i].events |= EVENT_WRITE;
if (kev[i].flags & EV_ERROR) events[i].events |= EVENT_ERROR;
if (kev[i].flags & EV_EOF) events[i].events |= EVENT_HUP;
}
return n;
}
#elif defined(USE_SELECT)
event_loop_t *event_loop_create(int max_events) {
event_loop_t *loop = calloc(1, sizeof(event_loop_t));
if (!loop) return NULL;
loop->max_events = max_events;
loop->fd_capacity = 64;
loop->fds = calloc(loop->fd_capacity, sizeof(*loop->fds));
if (!loop->fds) {
free(loop);
return NULL;
}
if (pal_mutex_init(&loop->mutex) != NWEDAV_OK) {
free(loop->fds);
free(loop);
return NULL;
}
FD_ZERO(&loop->read_set);
FD_ZERO(&loop->write_set);
FD_ZERO(&loop->error_set);
loop->max_fd = 0;
return loop;
}
void event_loop_destroy(event_loop_t *loop) {
if (!loop) return;
pal_mutex_destroy(&loop->mutex);
free(loop->fds);
free(loop);
}
int event_loop_add(event_loop_t *loop, socket_t fd, event_flags_t events, void *data) {
pal_mutex_lock(&loop->mutex);
if (loop->fd_count >= loop->fd_capacity) {
int new_cap = loop->fd_capacity * 2;
void *new_fds = realloc(loop->fds, new_cap * sizeof(*loop->fds));
if (!new_fds) {
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR_MEMORY;
}
loop->fds = new_fds;
loop->fd_capacity = new_cap;
}
loop->fds[loop->fd_count].fd = fd;
loop->fds[loop->fd_count].events = events;
loop->fds[loop->fd_count].data = data;
loop->fd_count++;
if (fd > loop->max_fd) loop->max_fd = fd;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
int event_loop_modify(event_loop_t *loop, socket_t fd, event_flags_t events) {
pal_mutex_lock(&loop->mutex);
for (int i = 0; i < loop->fd_count; i++) {
if (loop->fds[i].fd == fd) {
loop->fds[i].events = events;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
}
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR;
}
int event_loop_remove(event_loop_t *loop, socket_t fd) {
pal_mutex_lock(&loop->mutex);
for (int i = 0; i < loop->fd_count; i++) {
if (loop->fds[i].fd == fd) {
memmove(&loop->fds[i], &loop->fds[i + 1],
(loop->fd_count - i - 1) * sizeof(*loop->fds));
loop->fd_count--;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
}
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR;
}
int event_loop_wait(event_loop_t *loop, event_t *events, int max, int timeout_ms) {
fd_set read_set, write_set, error_set;
FD_ZERO(&read_set);
FD_ZERO(&write_set);
FD_ZERO(&error_set);
pal_mutex_lock(&loop->mutex);
int fd_snapshot_count = loop->fd_count;
struct {
socket_t fd;
event_flags_t events;
void *data;
} *fd_snapshot = NULL;
if (fd_snapshot_count > 0) {
fd_snapshot = alloca(fd_snapshot_count * sizeof(*fd_snapshot));
memcpy(fd_snapshot, loop->fds, fd_snapshot_count * sizeof(*fd_snapshot));
}
pal_mutex_unlock(&loop->mutex);
socket_t max_fd = 0;
for (int i = 0; i < fd_snapshot_count; i++) {
if (fd_snapshot[i].events & EVENT_READ) {
FD_SET(fd_snapshot[i].fd, &read_set);
}
if (fd_snapshot[i].events & EVENT_WRITE) {
FD_SET(fd_snapshot[i].fd, &write_set);
}
FD_SET(fd_snapshot[i].fd, &error_set);
if (fd_snapshot[i].fd > max_fd) max_fd = fd_snapshot[i].fd;
}
struct timeval tv, *ptv = NULL;
if (timeout_ms >= 0) {
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
ptv = &tv;
}
int ret = select((int)max_fd + 1, &read_set, &write_set, &error_set, ptv);
if (ret <= 0) return ret;
int count = 0;
for (int i = 0; i < fd_snapshot_count && count < max; i++) {
event_flags_t ev = 0;
if (FD_ISSET(fd_snapshot[i].fd, &read_set)) ev |= EVENT_READ;
if (FD_ISSET(fd_snapshot[i].fd, &write_set)) ev |= EVENT_WRITE;
if (FD_ISSET(fd_snapshot[i].fd, &error_set)) ev |= EVENT_ERROR;
if (ev) {
events[count].fd = fd_snapshot[i].fd;
events[count].events = ev;
events[count].user_data = fd_snapshot[i].data;
count++;
}
}
return count;
}
#else
event_loop_t *event_loop_create(int max_events) {
event_loop_t *loop = calloc(1, sizeof(event_loop_t));
if (!loop) return NULL;
loop->max_events = max_events;
loop->capacity = 64;
loop->pollfds = calloc(loop->capacity, sizeof(struct pollfd));
loop->userdata = calloc(loop->capacity, sizeof(void *));
if (!loop->pollfds || !loop->userdata) {
free(loop->pollfds);
free(loop->userdata);
free(loop);
return NULL;
}
if (pal_mutex_init(&loop->mutex) != NWEDAV_OK) {
free(loop->pollfds);
free(loop->userdata);
free(loop);
return NULL;
}
return loop;
}
void event_loop_destroy(event_loop_t *loop) {
if (!loop) return;
pal_mutex_destroy(&loop->mutex);
free(loop->pollfds);
free(loop->userdata);
free(loop);
}
int event_loop_add(event_loop_t *loop, socket_t fd, event_flags_t events, void *data) {
pal_mutex_lock(&loop->mutex);
if (loop->count >= loop->capacity) {
int new_cap = loop->capacity * 2;
struct pollfd *new_pfd = realloc(loop->pollfds, new_cap * sizeof(struct pollfd));
void **new_ud = realloc(loop->userdata, new_cap * sizeof(void *));
if (!new_pfd || !new_ud) {
free(new_pfd);
free(new_ud);
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR_MEMORY;
}
loop->pollfds = new_pfd;
loop->userdata = new_ud;
loop->capacity = new_cap;
}
loop->pollfds[loop->count].fd = fd;
loop->pollfds[loop->count].events = 0;
if (events & EVENT_READ) loop->pollfds[loop->count].events |= POLLIN;
if (events & EVENT_WRITE) loop->pollfds[loop->count].events |= POLLOUT;
loop->pollfds[loop->count].revents = 0;
loop->userdata[loop->count] = data;
loop->count++;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
int event_loop_modify(event_loop_t *loop, socket_t fd, event_flags_t events) {
pal_mutex_lock(&loop->mutex);
for (int i = 0; i < loop->count; i++) {
if (loop->pollfds[i].fd == fd) {
loop->pollfds[i].events = 0;
if (events & EVENT_READ) loop->pollfds[i].events |= POLLIN;
if (events & EVENT_WRITE) loop->pollfds[i].events |= POLLOUT;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
}
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR;
}
int event_loop_remove(event_loop_t *loop, socket_t fd) {
pal_mutex_lock(&loop->mutex);
for (int i = 0; i < loop->count; i++) {
if (loop->pollfds[i].fd == fd) {
memmove(&loop->pollfds[i], &loop->pollfds[i + 1],
(loop->count - i - 1) * sizeof(struct pollfd));
memmove(&loop->userdata[i], &loop->userdata[i + 1],
(loop->count - i - 1) * sizeof(void *));
loop->count--;
pal_mutex_unlock(&loop->mutex);
return NWEDAV_OK;
}
}
pal_mutex_unlock(&loop->mutex);
return NWEDAV_ERROR;
}
int event_loop_wait(event_loop_t *loop, event_t *events, int max, int timeout_ms) {
pal_mutex_lock(&loop->mutex);
int snapshot_count = loop->count;
struct pollfd *pfd_snapshot = NULL;
void **ud_snapshot = NULL;
if (snapshot_count > 0) {
pfd_snapshot = alloca(snapshot_count * sizeof(struct pollfd));
ud_snapshot = alloca(snapshot_count * sizeof(void *));
memcpy(pfd_snapshot, loop->pollfds, snapshot_count * sizeof(struct pollfd));
memcpy(ud_snapshot, loop->userdata, snapshot_count * sizeof(void *));
}
pal_mutex_unlock(&loop->mutex);
if (snapshot_count == 0) {
pal_sleep_ms(timeout_ms > 0 ? timeout_ms : 100);
return 0;
}
int ret = poll(pfd_snapshot, snapshot_count, timeout_ms);
if (ret <= 0) return ret;
int count = 0;
for (int i = 0; i < snapshot_count && count < max; i++) {
if (pfd_snapshot[i].revents) {
events[count].fd = pfd_snapshot[i].fd;
events[count].user_data = ud_snapshot[i];
events[count].events = 0;
if (pfd_snapshot[i].revents & POLLIN) events[count].events |= EVENT_READ;
if (pfd_snapshot[i].revents & POLLOUT) events[count].events |= EVENT_WRITE;
if (pfd_snapshot[i].revents & POLLERR) events[count].events |= EVENT_ERROR;
if (pfd_snapshot[i].revents & POLLHUP) events[count].events |= EVENT_HUP;
count++;
}
}
return count;
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#ifndef EVENTLOOP_H
#define EVENTLOOP_H
#include "nwedav.h"
#include "../platform/pal.h"
typedef struct {
socket_t fd;
event_flags_t events;
void *user_data;
} event_t;
typedef struct event_loop event_loop_t;
event_loop_t *event_loop_create(int max_events);
void event_loop_destroy(event_loop_t *loop);
int event_loop_add(event_loop_t *loop, socket_t fd, event_flags_t events, void *data);
int event_loop_modify(event_loop_t *loop, socket_t fd, event_flags_t events);
int event_loop_remove(event_loop_t *loop, socket_t fd);
int event_loop_wait(event_loop_t *loop, event_t *events, int max, int timeout_ms);
#endif
+205
View File
@@ -0,0 +1,205 @@
#include "threadpool.h"
#include <string.h>
typedef struct task {
task_func_t func;
void *arg;
struct task *next;
} task_t;
struct thread_pool {
thread_t *threads;
size_t thread_count;
task_t *queue_head;
task_t *queue_tail;
size_t queue_size;
mutex_t queue_mutex;
cond_t queue_cond;
cond_t done_cond;
volatile int shutdown;
volatile size_t active_tasks;
};
static void *worker_thread(void *arg) {
thread_pool_t *pool = (thread_pool_t *)arg;
while (1) {
pal_mutex_lock(&pool->queue_mutex);
while (!pool->shutdown && pool->queue_head == NULL) {
pal_cond_wait(&pool->queue_cond, &pool->queue_mutex);
}
if (pool->shutdown && pool->queue_head == NULL) {
pal_mutex_unlock(&pool->queue_mutex);
break;
}
task_t *task = pool->queue_head;
if (task) {
pool->queue_head = task->next;
if (pool->queue_head == NULL) {
pool->queue_tail = NULL;
}
pool->queue_size--;
pool->active_tasks++;
}
pal_mutex_unlock(&pool->queue_mutex);
if (task) {
task->func(task->arg);
free(task);
pal_mutex_lock(&pool->queue_mutex);
pool->active_tasks--;
if (pool->active_tasks == 0 && pool->queue_head == NULL) {
pal_cond_broadcast(&pool->done_cond);
}
pal_mutex_unlock(&pool->queue_mutex);
}
}
return NULL;
}
thread_pool_t *threadpool_create(size_t num_threads) {
thread_pool_t *pool = calloc(1, sizeof(thread_pool_t));
if (!pool) return NULL;
pool->thread_count = num_threads;
pool->threads = calloc(num_threads, sizeof(thread_t));
if (!pool->threads) {
free(pool);
return NULL;
}
if (pal_mutex_init(&pool->queue_mutex) != NWEDAV_OK) {
free(pool->threads);
free(pool);
return NULL;
}
if (pal_cond_init(&pool->queue_cond) != NWEDAV_OK) {
pal_mutex_destroy(&pool->queue_mutex);
free(pool->threads);
free(pool);
return NULL;
}
if (pal_cond_init(&pool->done_cond) != NWEDAV_OK) {
pal_cond_destroy(&pool->queue_cond);
pal_mutex_destroy(&pool->queue_mutex);
free(pool->threads);
free(pool);
return NULL;
}
for (size_t i = 0; i < num_threads; i++) {
if (pal_thread_create(&pool->threads[i], worker_thread, pool) != NWEDAV_OK) {
pool->shutdown = 1;
pal_cond_broadcast(&pool->queue_cond);
for (size_t j = 0; j < i; j++) {
pal_thread_join(pool->threads[j], NULL);
}
pal_cond_destroy(&pool->done_cond);
pal_cond_destroy(&pool->queue_cond);
pal_mutex_destroy(&pool->queue_mutex);
free(pool->threads);
free(pool);
return NULL;
}
}
return pool;
}
void threadpool_destroy(thread_pool_t *pool) {
if (!pool) return;
pal_mutex_lock(&pool->queue_mutex);
pool->shutdown = 1;
pal_cond_broadcast(&pool->queue_cond);
pal_mutex_unlock(&pool->queue_mutex);
for (size_t i = 0; i < pool->thread_count; i++) {
pal_thread_join(pool->threads[i], NULL);
}
task_t *task = pool->queue_head;
while (task) {
task_t *next = task->next;
free(task);
task = next;
}
pal_cond_destroy(&pool->done_cond);
pal_cond_destroy(&pool->queue_cond);
pal_mutex_destroy(&pool->queue_mutex);
free(pool->threads);
free(pool);
}
int threadpool_submit(thread_pool_t *pool, task_func_t func, void *arg) {
if (!pool || !func) return NWEDAV_ERROR;
task_t *task = calloc(1, sizeof(task_t));
if (!task) return NWEDAV_ERROR_MEMORY;
task->func = func;
task->arg = arg;
task->next = NULL;
pal_mutex_lock(&pool->queue_mutex);
if (pool->shutdown) {
pal_mutex_unlock(&pool->queue_mutex);
free(task);
return NWEDAV_ERROR;
}
if (pool->queue_tail) {
pool->queue_tail->next = task;
} else {
pool->queue_head = task;
}
pool->queue_tail = task;
pool->queue_size++;
pal_cond_signal(&pool->queue_cond);
pal_mutex_unlock(&pool->queue_mutex);
return NWEDAV_OK;
}
void threadpool_wait(thread_pool_t *pool) {
if (!pool) return;
pal_mutex_lock(&pool->queue_mutex);
while (pool->active_tasks > 0 || pool->queue_head != NULL) {
pal_cond_wait(&pool->done_cond, &pool->queue_mutex);
}
pal_mutex_unlock(&pool->queue_mutex);
}
size_t threadpool_pending(thread_pool_t *pool) {
if (!pool) return 0;
pal_mutex_lock(&pool->queue_mutex);
size_t size = pool->queue_size;
pal_mutex_unlock(&pool->queue_mutex);
return size;
}
size_t threadpool_active(thread_pool_t *pool) {
if (!pool) return 0;
pal_mutex_lock(&pool->queue_mutex);
size_t active = pool->active_tasks;
pal_mutex_unlock(&pool->queue_mutex);
return active;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include "nwedav.h"
#include "../platform/pal.h"
typedef void (*task_func_t)(void *arg);
typedef struct thread_pool thread_pool_t;
thread_pool_t *threadpool_create(size_t num_threads);
void threadpool_destroy(thread_pool_t *pool);
int threadpool_submit(thread_pool_t *pool, task_func_t func, void *arg);
void threadpool_wait(thread_pool_t *pool);
size_t threadpool_pending(thread_pool_t *pool);
size_t threadpool_active(thread_pool_t *pool);
#endif
+184
View File
@@ -0,0 +1,184 @@
#include "headers.h"
#include "../platform/memory.h"
#include "../platform/time.h"
#include <string.h>
static const struct {
const char *ext;
const char *mime;
} mime_types[] = {
{".html", "text/html"},
{".htm", "text/html"},
{".css", "text/css"},
{".js", "application/javascript"},
{".json", "application/json"},
{".xml", "application/xml"},
{".txt", "text/plain"},
{".md", "text/markdown"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".gif", "image/gif"},
{".ico", "image/x-icon"},
{".svg", "image/svg+xml"},
{".webp", "image/webp"},
{".bmp", "image/bmp"},
{".mp3", "audio/mpeg"},
{".wav", "audio/wav"},
{".ogg", "audio/ogg"},
{".m4a", "audio/mp4"},
{".flac", "audio/flac"},
{".mp4", "video/mp4"},
{".webm", "video/webm"},
{".avi", "video/x-msvideo"},
{".mkv", "video/x-matroska"},
{".mov", "video/quicktime"},
{".pdf", "application/pdf"},
{".doc", "application/msword"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls", "application/vnd.ms-excel"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".zip", "application/zip"},
{".tar", "application/x-tar"},
{".gz", "application/gzip"},
{".rar", "application/vnd.rar"},
{".7z", "application/x-7z-compressed"},
{".c", "text/x-c"},
{".h", "text/x-c"},
{".cpp", "text/x-c++src"},
{".py", "text/x-python"},
{".java", "text/x-java"},
{".sh", "application/x-sh"},
{".woff", "font/woff"},
{".woff2", "font/woff2"},
{".ttf", "font/ttf"},
{".otf", "font/otf"},
{".eot", "application/vnd.ms-fontobject"},
{NULL, NULL}
};
const char *http_get_mime_type(const char *extension) {
if (!extension) return "application/octet-stream";
for (int i = 0; mime_types[i].ext; i++) {
if (str_casecmp(extension, mime_types[i].ext) == 0) {
return mime_types[i].mime;
}
}
return "application/octet-stream";
}
const char *http_get_mime_type_for_file(const char *filename) {
if (!filename) return "application/octet-stream";
const char *dot = strrchr(filename, '.');
if (!dot) return "application/octet-stream";
return http_get_mime_type(dot);
}
int http_parse_range(const char *range_header, int64_t file_size,
int64_t *start, int64_t *end) {
if (!range_header || !start || !end) return NWEDAV_ERROR;
while (*range_header && *range_header == ' ') range_header++;
if (strncmp(range_header, "bytes=", 6) != 0) {
return NWEDAV_ERROR;
}
range_header += 6;
char *dash = strchr(range_header, '-');
if (!dash) return NWEDAV_ERROR;
if (dash == range_header) {
int64_t suffix_len = strtoll(dash + 1, NULL, 10);
*start = file_size - suffix_len;
*end = file_size - 1;
} else {
*start = strtoll(range_header, NULL, 10);
if (*(dash + 1) == '\0' || *(dash + 1) == ',') {
*end = file_size - 1;
} else {
*end = strtoll(dash + 1, NULL, 10);
}
}
if (*start < 0) *start = 0;
if (*end >= file_size) *end = file_size - 1;
if (*start > *end) return NWEDAV_ERROR;
return NWEDAV_OK;
}
int http_check_if_modified_since(const char *header, int64_t mtime) {
if (!header) return 1;
int64_t header_time = time_parse_http(header);
if (header_time == 0) return 1;
return mtime > header_time;
}
int http_check_if_none_match(const char *header, const char *etag) {
if (!header || !etag) return 1;
if (strcmp(header, "*") == 0) return 0;
const char *p = header;
while (*p) {
while (*p == ' ' || *p == ',') p++;
if (*p == '\0') break;
int weak = 0;
if (p[0] == 'W' && p[1] == '/') {
weak = 1;
p += 2;
}
(void)weak;
if (*p == '"') {
p++;
const char *end = strchr(p, '"');
if (end) {
size_t len = end - p;
size_t etag_len = strlen(etag);
const char *etag_val = etag;
if (etag[0] == '"') {
etag_val++;
etag_len -= 2;
}
if (len == etag_len && strncmp(p, etag_val, len) == 0) {
return 0;
}
p = end + 1;
}
} else {
break;
}
}
return 1;
}
int http_check_if_match(const char *header, const char *etag) {
if (!header || !etag) return 0;
if (strcmp(header, "*") == 0) return 1;
return !http_check_if_none_match(header, etag);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef HTTP_HEADERS_H
#define HTTP_HEADERS_H
#include "nwedav.h"
const char *http_get_mime_type(const char *extension);
const char *http_get_mime_type_for_file(const char *filename);
int http_parse_range(const char *range_header, int64_t file_size,
int64_t *start, int64_t *end);
int http_check_if_modified_since(const char *header, int64_t mtime);
int http_check_if_none_match(const char *header, const char *etag);
int http_check_if_match(const char *header, const char *etag);
#endif
+310
View File
@@ -0,0 +1,310 @@
#include "parser.h"
#include "../platform/memory.h"
#include <string.h>
#include <ctype.h>
static const struct {
const char *name;
http_method_t method;
} method_map[] = {
{"OPTIONS", HTTP_METHOD_OPTIONS},
{"GET", HTTP_METHOD_GET},
{"HEAD", HTTP_METHOD_HEAD},
{"PUT", HTTP_METHOD_PUT},
{"POST", HTTP_METHOD_POST},
{"DELETE", HTTP_METHOD_DELETE},
{"MKCOL", HTTP_METHOD_MKCOL},
{"COPY", HTTP_METHOD_COPY},
{"MOVE", HTTP_METHOD_MOVE},
{"PROPFIND", HTTP_METHOD_PROPFIND},
{"PROPPATCH", HTTP_METHOD_PROPPATCH},
{"LOCK", HTTP_METHOD_LOCK},
{"UNLOCK", HTTP_METHOD_UNLOCK},
{NULL, HTTP_METHOD_UNKNOWN}
};
void http_request_init(http_request_t *req) {
memset(req, 0, sizeof(http_request_t));
req->method = HTTP_METHOD_UNKNOWN;
req->version_major = 1;
req->version_minor = 1;
req->depth = -1;
req->overwrite = 1;
req->timeout = -1;
}
void http_request_free(http_request_t *req) {
if (!req) return;
free(req->uri);
free(req->query_string);
free(req->body);
free(req->host);
free(req->destination);
free(req->lock_token);
free(req->if_header);
free(req->content_type);
free(req->authorization);
http_header_t *h = req->headers;
while (h) {
http_header_t *next = h->next;
free(h->name);
free(h->value);
free(h);
h = next;
}
memset(req, 0, sizeof(http_request_t));
}
http_method_t http_method_from_string(const char *method) {
for (int i = 0; method_map[i].name; i++) {
if (strcmp(method, method_map[i].name) == 0) {
return method_map[i].method;
}
}
return HTTP_METHOD_UNKNOWN;
}
const char *http_method_to_string(http_method_t method) {
for (int i = 0; method_map[i].name; i++) {
if (method_map[i].method == method) {
return method_map[i].name;
}
}
return "UNKNOWN";
}
int http_parse_request_line(const char *data, size_t len, http_request_t *req, size_t *consumed) {
const char *end = data + len;
const char *p = data;
const char *method_end = memchr(p, ' ', end - p);
if (!method_end) return NWEDAV_ERROR_PARSE;
char method[16];
size_t method_len = method_end - p;
if (method_len >= sizeof(method)) return NWEDAV_ERROR_PARSE;
memcpy(method, p, method_len);
method[method_len] = '\0';
req->method = http_method_from_string(method);
p = method_end + 1;
const char *uri_end = memchr(p, ' ', end - p);
if (!uri_end) return NWEDAV_ERROR_PARSE;
size_t uri_len = uri_end - p;
if (uri_len > NWEDAV_MAX_URI_LENGTH) return NWEDAV_ERROR_PARSE;
const char *query = memchr(p, '?', uri_len);
if (query) {
req->uri = str_ndup(p, query - p);
req->query_string = str_ndup(query + 1, uri_end - query - 1);
} else {
req->uri = str_ndup(p, uri_len);
}
char decoded[NWEDAV_MAX_URI_LENGTH];
if (http_url_decode(req->uri, decoded, sizeof(decoded)) == NWEDAV_OK) {
free(req->uri);
req->uri = str_dup(decoded);
}
p = uri_end + 1;
if (end - p < 8) return NWEDAV_ERROR_PARSE;
if (strncmp(p, "HTTP/", 5) != 0) return NWEDAV_ERROR_PARSE;
p += 5;
req->version_major = *p - '0';
p += 2;
req->version_minor = *p - '0';
const char *line_end = memmem(p, end - p, "\r\n", 2);
if (!line_end) return NWEDAV_ERROR_PARSE;
*consumed = (line_end + 2) - data;
return NWEDAV_OK;
}
static int parse_depth(const char *value) {
if (!value) return -1;
while (isspace((unsigned char)*value)) value++;
if (strcmp(value, "0") == 0) return LOCK_DEPTH_0;
if (strcmp(value, "1") == 0) return LOCK_DEPTH_1;
if (str_casecmp(value, "infinity") == 0) return LOCK_DEPTH_INFINITY;
return -1;
}
static int parse_timeout(const char *value) {
if (!value) return -1;
while (isspace((unsigned char)*value)) value++;
if (str_casecmp(value, "Infinite") == 0) return -1;
if (str_ncasecmp(value, "Second-", 7) == 0) {
return atoi(value + 7);
}
return -1;
}
int http_parse_headers(const char *data, size_t len, http_request_t *req, size_t *consumed) {
const char *end = data + len;
const char *p = data;
while (p < end - 1) {
if (p[0] == '\r' && p[1] == '\n') {
*consumed = (p + 2) - data;
return NWEDAV_OK;
}
const char *colon = memchr(p, ':', end - p);
if (!colon) return NWEDAV_ERROR_PARSE;
const char *line_end = memmem(p, end - p, "\r\n", 2);
if (!line_end) return NWEDAV_ERROR_PARSE;
size_t name_len = colon - p;
char *name = str_ndup(p, name_len);
const char *value_start = colon + 1;
while (value_start < line_end && isspace((unsigned char)*value_start)) {
value_start++;
}
const char *value_end = line_end;
while (value_end > value_start && isspace((unsigned char)*(value_end - 1))) {
value_end--;
}
char *value = str_ndup(value_start, value_end - value_start);
if (str_casecmp(name, "Host") == 0) {
free(req->host);
req->host = str_dup(value);
} else if (str_casecmp(name, "Content-Length") == 0) {
req->content_length = (size_t)strtoull(value, NULL, 10);
} else if (str_casecmp(name, "Transfer-Encoding") == 0) {
req->chunked = (strstr(value, "chunked") != NULL);
} else if (str_casecmp(name, "Content-Type") == 0) {
free(req->content_type);
req->content_type = str_dup(value);
} else if (str_casecmp(name, "Authorization") == 0) {
free(req->authorization);
req->authorization = str_dup(value);
} else if (str_casecmp(name, "Destination") == 0) {
free(req->destination);
char decoded[NWEDAV_MAX_URI_LENGTH];
if (http_url_decode(value, decoded, sizeof(decoded)) == NWEDAV_OK) {
req->destination = str_dup(decoded);
} else {
req->destination = str_dup(value);
}
} else if (str_casecmp(name, "Depth") == 0) {
req->depth = parse_depth(value);
} else if (str_casecmp(name, "Overwrite") == 0) {
req->overwrite = (value[0] == 'T' || value[0] == 't');
} else if (str_casecmp(name, "Lock-Token") == 0) {
free(req->lock_token);
req->lock_token = str_dup(value);
} else if (str_casecmp(name, "If") == 0) {
free(req->if_header);
req->if_header = str_dup(value);
} else if (str_casecmp(name, "Timeout") == 0) {
req->timeout = parse_timeout(value);
}
http_header_t *header = calloc(1, sizeof(http_header_t));
header->name = name;
header->value = value;
header->next = req->headers;
req->headers = header;
p = line_end + 2;
}
return NWEDAV_ERROR_PARSE;
}
int http_parse_request(const char *data, size_t len, http_request_t *req, size_t *consumed) {
size_t line_consumed = 0;
size_t headers_consumed = 0;
int ret = http_parse_request_line(data, len, req, &line_consumed);
if (ret != NWEDAV_OK) return ret;
ret = http_parse_headers(data + line_consumed, len - line_consumed, req, &headers_consumed);
if (ret != NWEDAV_OK) return ret;
*consumed = line_consumed + headers_consumed;
return NWEDAV_OK;
}
const char *http_get_header(http_request_t *req, const char *name) {
http_header_t *h = req->headers;
while (h) {
if (str_casecmp(h->name, name) == 0) {
return h->value;
}
h = h->next;
}
return NULL;
}
int http_add_header(http_request_t *req, const char *name, const char *value) {
http_header_t *header = calloc(1, sizeof(http_header_t));
if (!header) return NWEDAV_ERROR_MEMORY;
header->name = str_dup(name);
header->value = str_dup(value);
header->next = req->headers;
req->headers = header;
return NWEDAV_OK;
}
int http_url_decode(const char *src, char *dst, size_t dst_size) {
if (!src || !dst || dst_size == 0) return NWEDAV_ERROR;
size_t di = 0;
while (*src && di < dst_size - 1) {
if (*src == '%' && isxdigit((unsigned char)src[1]) && isxdigit((unsigned char)src[2])) {
int hi = src[1];
int lo = src[2];
hi = (hi >= 'a') ? hi - 'a' + 10 : (hi >= 'A') ? hi - 'A' + 10 : hi - '0';
lo = (lo >= 'a') ? lo - 'a' + 10 : (lo >= 'A') ? lo - 'A' + 10 : lo - '0';
dst[di++] = (char)((hi << 4) | lo);
src += 3;
} else if (*src == '+') {
dst[di++] = ' ';
src++;
} else {
dst[di++] = *src++;
}
}
dst[di] = '\0';
return NWEDAV_OK;
}
int http_url_encode(const char *src, char *dst, size_t dst_size) {
if (!src || !dst || dst_size == 0) return NWEDAV_ERROR;
static const char *hex = "0123456789ABCDEF";
size_t di = 0;
while (*src && di < dst_size - 3) {
unsigned char c = *src++;
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
dst[di++] = c;
} else {
dst[di++] = '%';
dst[di++] = hex[c >> 4];
dst[di++] = hex[c & 0x0f];
}
}
dst[di] = '\0';
return NWEDAV_OK;
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef HTTP_PARSER_H
#define HTTP_PARSER_H
#include "nwedav.h"
typedef struct http_header {
char *name;
char *value;
struct http_header *next;
} http_header_t;
typedef struct {
http_method_t method;
char *uri;
char *query_string;
int version_major;
int version_minor;
http_header_t *headers;
char *body;
size_t body_len;
size_t content_length;
int chunked;
char *host;
char *destination;
char *lock_token;
char *if_header;
int depth;
int overwrite;
int timeout;
char *content_type;
char *authorization;
} http_request_t;
void http_request_init(http_request_t *req);
void http_request_free(http_request_t *req);
int http_parse_request_line(const char *data, size_t len, http_request_t *req, size_t *consumed);
int http_parse_headers(const char *data, size_t len, http_request_t *req, size_t *consumed);
int http_parse_request(const char *data, size_t len, http_request_t *req, size_t *consumed);
const char *http_get_header(http_request_t *req, const char *name);
int http_add_header(http_request_t *req, const char *name, const char *value);
http_method_t http_method_from_string(const char *method);
const char *http_method_to_string(http_method_t method);
int http_url_decode(const char *src, char *dst, size_t dst_size);
int http_url_encode(const char *src, char *dst, size_t dst_size);
#endif
+249
View File
@@ -0,0 +1,249 @@
#include "response.h"
#include <string.h>
#include <stdio.h>
static const struct {
http_status_t code;
const char *text;
} status_texts[] = {
{HTTP_100_CONTINUE, "Continue"},
{HTTP_200_OK, "OK"},
{HTTP_201_CREATED, "Created"},
{HTTP_204_NO_CONTENT, "No Content"},
{HTTP_207_MULTI_STATUS, "Multi-Status"},
{HTTP_301_MOVED_PERMANENTLY, "Moved Permanently"},
{HTTP_302_FOUND, "Found"},
{HTTP_304_NOT_MODIFIED, "Not Modified"},
{HTTP_400_BAD_REQUEST, "Bad Request"},
{HTTP_401_UNAUTHORIZED, "Unauthorized"},
{HTTP_403_FORBIDDEN, "Forbidden"},
{HTTP_404_NOT_FOUND, "Not Found"},
{HTTP_405_METHOD_NOT_ALLOWED, "Method Not Allowed"},
{HTTP_409_CONFLICT, "Conflict"},
{HTTP_412_PRECONDITION_FAILED, "Precondition Failed"},
{HTTP_413_PAYLOAD_TOO_LARGE, "Payload Too Large"},
{HTTP_415_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"},
{HTTP_423_LOCKED, "Locked"},
{HTTP_424_FAILED_DEPENDENCY, "Failed Dependency"},
{HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error"},
{HTTP_501_NOT_IMPLEMENTED, "Not Implemented"},
{HTTP_502_BAD_GATEWAY, "Bad Gateway"},
{HTTP_503_SERVICE_UNAVAILABLE, "Service Unavailable"},
{HTTP_507_INSUFFICIENT_STORAGE, "Insufficient Storage"},
{0, NULL}
};
const char *http_status_text(http_status_t status) {
for (int i = 0; status_texts[i].text; i++) {
if (status_texts[i].code == status) {
return status_texts[i].text;
}
}
return "Unknown";
}
void http_response_init(http_response_t *res) {
memset(res, 0, sizeof(http_response_t));
res->status = HTTP_200_OK;
res->keep_alive = 1;
}
void http_response_free(http_response_t *res) {
if (!res) return;
response_header_t *h = res->headers;
while (h) {
response_header_t *next = h->next;
free(h->name);
free(h->value);
free(h);
h = next;
}
free(res->body);
memset(res, 0, sizeof(http_response_t));
}
void http_response_set_status(http_response_t *res, http_status_t status) {
res->status = status;
}
int http_response_add_header(http_response_t *res, const char *name, const char *value) {
response_header_t *existing = res->headers;
while (existing) {
if (str_casecmp(existing->name, name) == 0) {
free(existing->value);
existing->value = str_dup(value);
return NWEDAV_OK;
}
existing = existing->next;
}
response_header_t *h = calloc(1, sizeof(response_header_t));
if (!h) return NWEDAV_ERROR_MEMORY;
h->name = str_dup(name);
h->value = str_dup(value);
h->next = res->headers;
res->headers = h;
return NWEDAV_OK;
}
int http_response_set_body(http_response_t *res, const char *body, size_t len) {
free(res->body);
if (!body || len == 0) {
res->body = NULL;
res->body_len = 0;
return NWEDAV_OK;
}
res->body = malloc(len + 1);
if (!res->body) return NWEDAV_ERROR_MEMORY;
memcpy(res->body, body, len);
res->body[len] = '\0';
res->body_len = len;
return NWEDAV_OK;
}
int http_response_set_body_str(http_response_t *res, const char *body) {
return http_response_set_body(res, body, body ? strlen(body) : 0);
}
int http_response_build(http_response_t *res, buffer_t *buf) {
buffer_append_fmt(buf, "HTTP/1.1 %d %s\r\n", res->status, http_status_text(res->status));
response_header_t *h = res->headers;
int has_content_length = 0;
int has_content_type = 0;
int has_connection = 0;
int has_server = 0;
int has_date = 0;
while (h) {
if (str_casecmp(h->name, "Content-Length") == 0) has_content_length = 1;
if (str_casecmp(h->name, "Content-Type") == 0) has_content_type = 1;
if (str_casecmp(h->name, "Connection") == 0) has_connection = 1;
if (str_casecmp(h->name, "Server") == 0) has_server = 1;
if (str_casecmp(h->name, "Date") == 0) has_date = 1;
buffer_append_fmt(buf, "%s: %s\r\n", h->name, h->value);
h = h->next;
}
if (!has_server) {
buffer_append_str(buf, "Server: nwedav/" NWEDAV_VERSION_STRING "\r\n");
}
if (!has_date) {
time_t now = time(NULL);
struct tm *tm = gmtime(&now);
char date[64];
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", tm);
buffer_append_fmt(buf, "Date: %s\r\n", date);
}
if (!has_connection) {
buffer_append_str(buf, res->keep_alive ? "Connection: keep-alive\r\n" : "Connection: close\r\n");
}
if (!has_content_length && !res->chunked) {
buffer_append_fmt(buf, "Content-Length: %zu\r\n", res->body_len);
}
if (!has_content_type && res->body_len > 0) {
buffer_append_str(buf, "Content-Type: text/plain\r\n");
}
buffer_append_str(buf, "\r\n");
if (res->body && res->body_len > 0) {
buffer_append(buf, res->body, res->body_len);
}
return NWEDAV_OK;
}
int http_response_build_headers_only(http_response_t *res, buffer_t *buf) {
buffer_append_fmt(buf, "HTTP/1.1 %d %s\r\n", res->status, http_status_text(res->status));
response_header_t *h = res->headers;
while (h) {
buffer_append_fmt(buf, "%s: %s\r\n", h->name, h->value);
h = h->next;
}
int has_server = 0;
int has_date = 0;
h = res->headers;
while (h) {
if (str_casecmp(h->name, "Server") == 0) has_server = 1;
if (str_casecmp(h->name, "Date") == 0) has_date = 1;
h = h->next;
}
if (!has_server) {
buffer_append_str(buf, "Server: nwedav/" NWEDAV_VERSION_STRING "\r\n");
}
if (!has_date) {
time_t now = time(NULL);
struct tm *tm = gmtime(&now);
char date[64];
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", tm);
buffer_append_fmt(buf, "Date: %s\r\n", date);
}
buffer_append_str(buf, "\r\n");
return NWEDAV_OK;
}
int http_response_error(http_response_t *res, http_status_t status, const char *message) {
http_response_set_status(res, status);
char body[1024];
snprintf(body, sizeof(body),
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>%d %s</title></head>\n"
"<body>\n"
"<h1>%d %s</h1>\n"
"<p>%s</p>\n"
"</body>\n"
"</html>\n",
status, http_status_text(status),
status, http_status_text(status),
message ? message : http_status_text(status));
http_response_add_header(res, "Content-Type", "text/html; charset=utf-8");
http_response_set_body_str(res, body);
return NWEDAV_OK;
}
int http_response_redirect(http_response_t *res, http_status_t status, const char *location) {
http_response_set_status(res, status);
http_response_add_header(res, "Location", location);
char body[1024];
snprintf(body, sizeof(body),
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>%d %s</title></head>\n"
"<body>\n"
"<h1>%d %s</h1>\n"
"<p>The document has moved <a href=\"%s\">here</a>.</p>\n"
"</body>\n"
"</html>\n",
status, http_status_text(status),
status, http_status_text(status),
location);
http_response_add_header(res, "Content-Type", "text/html; charset=utf-8");
http_response_set_body_str(res, body);
return NWEDAV_OK;
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef HTTP_RESPONSE_H
#define HTTP_RESPONSE_H
#include "nwedav.h"
#include "../platform/memory.h"
typedef struct response_header {
char *name;
char *value;
struct response_header *next;
} response_header_t;
typedef struct {
http_status_t status;
response_header_t *headers;
char *body;
size_t body_len;
int chunked;
int keep_alive;
} http_response_t;
void http_response_init(http_response_t *res);
void http_response_free(http_response_t *res);
void http_response_set_status(http_response_t *res, http_status_t status);
int http_response_add_header(http_response_t *res, const char *name, const char *value);
int http_response_set_body(http_response_t *res, const char *body, size_t len);
int http_response_set_body_str(http_response_t *res, const char *body);
int http_response_build(http_response_t *res, buffer_t *buf);
int http_response_build_headers_only(http_response_t *res, buffer_t *buf);
const char *http_status_text(http_status_t status);
int http_response_error(http_response_t *res, http_status_t status, const char *message);
int http_response_redirect(http_response_t *res, http_status_t status, const char *location);
#endif
+139
View File
@@ -0,0 +1,139 @@
#include "core/server.h"
#include "core/config.h"
#include "core/logging.h"
#include "platform/pal.h"
#include "platform/file.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static void print_version(void) {
printf("nwedav WebDAV Server %s\n", NWEDAV_VERSION_STRING);
printf("Enterprise WebDAV server implementation in pure C\n");
}
static void print_usage(void) {
printf("Usage: nwedav [options]\n\n");
printf("Options:\n");
printf(" -c, --config <path> Configuration file path\n");
printf(" -p, --port <port> Listen port (default: 8080)\n");
printf(" -b, --bind <addr> Bind address (default: 0.0.0.0)\n");
printf(" -d, --data <path> Data directory path\n");
printf(" -f, --foreground Run in foreground\n");
printf(" -v, --verbose Verbose output\n");
printf(" -V, --version Show version\n");
printf(" -h, --help Show this help\n");
}
int main(int argc, char **argv) {
config_t config;
config_set_defaults(&config);
char config_path[PATH_MAX] = "";
int foreground = 1;
int verbose = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage();
return 0;
} else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
print_version();
return 0;
} else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) {
if (i + 1 < argc) {
strncpy(config_path, argv[++i], sizeof(config_path) - 1);
}
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
if (i + 1 < argc) {
config.port = (uint16_t)atoi(argv[++i]);
}
} else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--bind") == 0) {
if (i + 1 < argc) {
strncpy(config.bind_address, argv[++i], sizeof(config.bind_address) - 1);
}
} else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--data") == 0) {
if (i + 1 < argc) {
strncpy(config.storage.root_path, argv[++i], sizeof(config.storage.root_path) - 1);
}
} else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--foreground") == 0) {
foreground = 1;
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
verbose = 1;
config.logging.level = LOG_LEVEL_DEBUG;
}
}
if (config_path[0]) {
if (config_load(config_path, &config) != NWEDAV_OK) {
fprintf(stderr, "Failed to load config from '%s'\n", config_path);
return 1;
}
}
if (config_validate(&config) != NWEDAV_OK) {
fprintf(stderr, "Invalid configuration\n");
return 1;
}
if (pal_init() != NWEDAV_OK) {
fprintf(stderr, "Failed to initialize platform\n");
return 1;
}
g_logger = logger_create(config.logging.file[0] ? config.logging.file : NULL,
verbose ? LOG_LEVEL_DEBUG : config.logging.level);
if (!g_logger) {
fprintf(stderr, "Failed to create logger\n");
pal_cleanup();
return 1;
}
LOG_INFO("Starting nwedav WebDAV Server %s", NWEDAV_VERSION_STRING);
if (!file_exists(config.storage.root_path)) {
LOG_INFO("Creating storage directory: %s", config.storage.root_path);
if (file_mkdir_recursive(config.storage.root_path) != NWEDAV_OK) {
LOG_ERROR("Failed to create storage directory");
logger_destroy(g_logger);
pal_cleanup();
return 1;
}
}
if (!file_exists(config.storage.temp_path)) {
file_mkdir_recursive(config.storage.temp_path);
}
server_t *server = server_create(&config);
if (!server) {
LOG_ERROR("Failed to create server");
logger_destroy(g_logger);
pal_cleanup();
return 1;
}
if (server_start(server) != NWEDAV_OK) {
LOG_ERROR("Failed to start server");
server_destroy(server);
logger_destroy(g_logger);
pal_cleanup();
return 1;
}
printf("nwedav WebDAV Server %s running on http://%s:%d\n",
NWEDAV_VERSION_STRING, config.bind_address, config.port);
printf("Press Ctrl+C to stop\n");
(void)foreground;
server_run(server);
LOG_INFO("Shutting down...");
server_destroy(server);
logger_destroy(g_logger);
pal_cleanup();
return 0;
}
+642
View File
@@ -0,0 +1,642 @@
#include "file.h"
#include <string.h>
#ifdef _WIN32
static wchar_t *utf8_to_wchar(const char *str) {
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
wchar_t *wstr = malloc(len * sizeof(wchar_t));
if (wstr) {
MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);
}
return wstr;
}
static char *wchar_to_utf8(const wchar_t *wstr) {
int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char *str = malloc(len);
if (str) {
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
}
return str;
}
#endif
file_handle_t file_open(const char *path, int flags) {
file_handle_t fh;
#ifdef _WIN32
DWORD access = 0;
DWORD creation = 0;
if (flags & FILE_READ) access |= GENERIC_READ;
if (flags & FILE_WRITE) access |= GENERIC_WRITE;
if (flags & FILE_CREATE) {
if (flags & FILE_TRUNC) {
creation = CREATE_ALWAYS;
} else {
creation = OPEN_ALWAYS;
}
} else {
if (flags & FILE_TRUNC) {
creation = TRUNCATE_EXISTING;
} else {
creation = OPEN_EXISTING;
}
}
wchar_t *wpath = utf8_to_wchar(path);
fh.handle = CreateFileW(wpath, access, FILE_SHARE_READ, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
free(wpath);
#else
int oflags = 0;
if ((flags & FILE_READ) && (flags & FILE_WRITE)) {
oflags = O_RDWR;
} else if (flags & FILE_WRITE) {
oflags = O_WRONLY;
} else {
oflags = O_RDONLY;
}
if (flags & FILE_CREATE) oflags |= O_CREAT;
if (flags & FILE_TRUNC) oflags |= O_TRUNC;
if (flags & FILE_APPEND) oflags |= O_APPEND;
fh.fd = open(path, oflags, 0644);
#endif
return fh;
}
int file_close(file_handle_t fh) {
#ifdef _WIN32
return CloseHandle(fh.handle) ? NWEDAV_OK : NWEDAV_ERROR_IO;
#else
return close(fh.fd) == 0 ? NWEDAV_OK : NWEDAV_ERROR_IO;
#endif
}
ssize_t file_read(file_handle_t fh, void *buf, size_t count) {
#ifdef _WIN32
DWORD read_bytes;
if (!ReadFile(fh.handle, buf, (DWORD)count, &read_bytes, NULL)) {
return -1;
}
return (ssize_t)read_bytes;
#else
return read(fh.fd, buf, count);
#endif
}
ssize_t file_write(file_handle_t fh, const void *buf, size_t count) {
#ifdef _WIN32
DWORD written;
if (!WriteFile(fh.handle, buf, (DWORD)count, &written, NULL)) {
return -1;
}
return (ssize_t)written;
#else
return write(fh.fd, buf, count);
#endif
}
int64_t file_seek(file_handle_t fh, int64_t offset, int whence) {
#ifdef _WIN32
LARGE_INTEGER li;
li.QuadPart = offset;
DWORD method = FILE_BEGIN;
if (whence == SEEK_CUR) method = FILE_CURRENT;
else if (whence == SEEK_END) method = FILE_END;
li.LowPart = SetFilePointer(fh.handle, li.LowPart, &li.HighPart, method);
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
return -1;
}
return li.QuadPart;
#else
return lseek(fh.fd, offset, whence);
#endif
}
int64_t file_tell(file_handle_t fh) {
return file_seek(fh, 0, SEEK_CUR);
}
int file_stat(const char *path, file_stat_t *st) {
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
WIN32_FILE_ATTRIBUTE_DATA data;
if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &data)) {
free(wpath);
return NWEDAV_ERROR_NOT_FOUND;
}
free(wpath);
ULARGE_INTEGER size;
size.LowPart = data.nFileSizeLow;
size.HighPart = data.nFileSizeHigh;
st->size = size.QuadPart;
ULARGE_INTEGER time;
time.LowPart = data.ftLastWriteTime.dwLowDateTime;
time.HighPart = data.ftLastWriteTime.dwHighDateTime;
st->mtime = (int64_t)((time.QuadPart - 116444736000000000ULL) / 10000000ULL);
time.LowPart = data.ftCreationTime.dwLowDateTime;
time.HighPart = data.ftCreationTime.dwHighDateTime;
st->ctime = (int64_t)((time.QuadPart - 116444736000000000ULL) / 10000000ULL);
st->is_dir = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
st->is_file = !st->is_dir;
st->is_link = (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ? 1 : 0;
st->mode = 0;
#else
struct stat sb;
if (stat(path, &sb) != 0) {
return NWEDAV_ERROR_NOT_FOUND;
}
st->size = sb.st_size;
st->mtime = sb.st_mtime;
st->ctime = sb.st_ctime;
st->is_dir = S_ISDIR(sb.st_mode);
st->is_file = S_ISREG(sb.st_mode);
st->is_link = S_ISLNK(sb.st_mode);
st->mode = sb.st_mode;
#endif
return NWEDAV_OK;
}
int file_fstat(file_handle_t fh, file_stat_t *st) {
#ifdef _WIN32
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(fh.handle, &info)) {
return NWEDAV_ERROR_IO;
}
ULARGE_INTEGER size;
size.LowPart = info.nFileSizeLow;
size.HighPart = info.nFileSizeHigh;
st->size = size.QuadPart;
ULARGE_INTEGER time;
time.LowPart = info.ftLastWriteTime.dwLowDateTime;
time.HighPart = info.ftLastWriteTime.dwHighDateTime;
st->mtime = (int64_t)((time.QuadPart - 116444736000000000ULL) / 10000000ULL);
time.LowPart = info.ftCreationTime.dwLowDateTime;
time.HighPart = info.ftCreationTime.dwHighDateTime;
st->ctime = (int64_t)((time.QuadPart - 116444736000000000ULL) / 10000000ULL);
st->is_dir = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
st->is_file = !st->is_dir;
st->is_link = 0;
st->mode = 0;
#else
struct stat sb;
if (fstat(fh.fd, &sb) != 0) {
return NWEDAV_ERROR_IO;
}
st->size = sb.st_size;
st->mtime = sb.st_mtime;
st->ctime = sb.st_ctime;
st->is_dir = S_ISDIR(sb.st_mode);
st->is_file = S_ISREG(sb.st_mode);
st->is_link = S_ISLNK(sb.st_mode);
st->mode = sb.st_mode;
#endif
return NWEDAV_OK;
}
int file_exists(const char *path) {
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
DWORD attrs = GetFileAttributesW(wpath);
free(wpath);
return attrs != INVALID_FILE_ATTRIBUTES;
#else
return access(path, F_OK) == 0;
#endif
}
int file_is_dir(const char *path) {
file_stat_t st;
if (file_stat(path, &st) != NWEDAV_OK) {
return 0;
}
return st.is_dir;
}
int file_mkdir(const char *path) {
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
int ret = CreateDirectoryW(wpath, NULL) ? NWEDAV_OK : NWEDAV_ERROR_IO;
free(wpath);
return ret;
#else
return mkdir(path, 0755) == 0 ? NWEDAV_OK : NWEDAV_ERROR_IO;
#endif
}
int file_mkdir_recursive(const char *path) {
char tmp[PATH_MAX];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp), "%s", path);
len = strlen(tmp);
if (tmp[len - 1] == PATH_SEPARATOR) {
tmp[len - 1] = 0;
}
for (p = tmp + 1; *p; p++) {
if (*p == PATH_SEPARATOR) {
*p = 0;
if (!file_exists(tmp)) {
if (file_mkdir(tmp) != NWEDAV_OK) {
return NWEDAV_ERROR_IO;
}
}
*p = PATH_SEPARATOR;
}
}
if (!file_exists(tmp)) {
return file_mkdir(tmp);
}
return NWEDAV_OK;
}
int file_rmdir(const char *path) {
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
int ret = RemoveDirectoryW(wpath) ? NWEDAV_OK : NWEDAV_ERROR_IO;
free(wpath);
return ret;
#else
return rmdir(path) == 0 ? NWEDAV_OK : NWEDAV_ERROR_IO;
#endif
}
int file_unlink(const char *path) {
#ifdef _WIN32
wchar_t *wpath = utf8_to_wchar(path);
int ret = DeleteFileW(wpath) ? NWEDAV_OK : NWEDAV_ERROR_IO;
free(wpath);
return ret;
#else
return unlink(path) == 0 ? NWEDAV_OK : NWEDAV_ERROR_IO;
#endif
}
int file_rename(const char *oldpath, const char *newpath) {
#ifdef _WIN32
wchar_t *wold = utf8_to_wchar(oldpath);
wchar_t *wnew = utf8_to_wchar(newpath);
int ret = MoveFileExW(wold, wnew, MOVEFILE_REPLACE_EXISTING) ? NWEDAV_OK : NWEDAV_ERROR_IO;
free(wold);
free(wnew);
return ret;
#else
return rename(oldpath, newpath) == 0 ? NWEDAV_OK : NWEDAV_ERROR_IO;
#endif
}
int file_copy(const char *src, const char *dst) {
file_handle_t src_fh = file_open(src, FILE_READ);
#ifdef _WIN32
if (src_fh.handle == INVALID_HANDLE_VALUE) return NWEDAV_ERROR_NOT_FOUND;
#else
if (src_fh.fd < 0) return NWEDAV_ERROR_NOT_FOUND;
#endif
file_handle_t dst_fh = file_open(dst, FILE_WRITE | FILE_CREATE | FILE_TRUNC);
#ifdef _WIN32
if (dst_fh.handle == INVALID_HANDLE_VALUE) {
#else
if (dst_fh.fd < 0) {
#endif
file_close(src_fh);
return NWEDAV_ERROR_IO;
}
char buf[65536];
ssize_t n;
while ((n = file_read(src_fh, buf, sizeof(buf))) > 0) {
ssize_t written = 0;
while (written < n) {
ssize_t w = file_write(dst_fh, buf + written, n - written);
if (w < 0) {
file_close(src_fh);
file_close(dst_fh);
return NWEDAV_ERROR_IO;
}
written += w;
}
}
file_close(src_fh);
file_close(dst_fh);
return NWEDAV_OK;
}
int file_copy_recursive(const char *src, const char *dst) {
file_stat_t st;
if (file_stat(src, &st) != NWEDAV_OK) {
return NWEDAV_ERROR_NOT_FOUND;
}
if (st.is_file) {
return file_copy(src, dst);
}
if (file_mkdir(dst) != NWEDAV_OK && !file_exists(dst)) {
return NWEDAV_ERROR_IO;
}
dir_handle_t *dh = dir_open(src);
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 src_path[PATH_MAX];
char dst_path[PATH_MAX];
path_join(src_path, sizeof(src_path), src, entry.name);
path_join(dst_path, sizeof(dst_path), dst, entry.name);
int ret = file_copy_recursive(src_path, dst_path);
if (ret != NWEDAV_OK) {
dir_close(dh);
return ret;
}
}
dir_close(dh);
return NWEDAV_OK;
}
int file_delete_recursive(const char *path) {
file_stat_t st;
if (file_stat(path, &st) != NWEDAV_OK) {
return NWEDAV_OK;
}
if (st.is_file) {
return file_unlink(path);
}
dir_handle_t *dh = dir_open(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 full_path[PATH_MAX];
path_join(full_path, sizeof(full_path), path, entry.name);
int ret = file_delete_recursive(full_path);
if (ret != NWEDAV_OK) {
dir_close(dh);
return ret;
}
}
dir_close(dh);
return file_rmdir(path);
}
int64_t file_size(const char *path) {
file_stat_t st;
if (file_stat(path, &st) != NWEDAV_OK) {
return -1;
}
return st.size;
}
dir_handle_t *dir_open(const char *path) {
dir_handle_t *dh = malloc(sizeof(dir_handle_t));
if (!dh) return NULL;
#ifdef _WIN32
char pattern[PATH_MAX];
snprintf(pattern, sizeof(pattern), "%s\\*", path);
wchar_t *wpattern = utf8_to_wchar(pattern);
dh->handle = FindFirstFileW(wpattern, &dh->data);
free(wpattern);
if (dh->handle == INVALID_HANDLE_VALUE) {
free(dh);
return NULL;
}
dh->first = 1;
#else
dh->dir = opendir(path);
if (!dh->dir) {
free(dh);
return NULL;
}
#endif
return dh;
}
int dir_close(dir_handle_t *dh) {
if (!dh) return NWEDAV_ERROR;
#ifdef _WIN32
FindClose(dh->handle);
#else
closedir(dh->dir);
#endif
free(dh);
return NWEDAV_OK;
}
int dir_read(dir_handle_t *dh, dir_entry_t *entry) {
if (!dh || !entry) return NWEDAV_ERROR;
#ifdef _WIN32
if (dh->first) {
dh->first = 0;
} else {
if (!FindNextFileW(dh->handle, &dh->data)) {
return NWEDAV_ERROR;
}
}
char *name = wchar_to_utf8(dh->data.cFileName);
strncpy(entry->name, name, sizeof(entry->name) - 1);
entry->name[sizeof(entry->name) - 1] = '\0';
free(name);
entry->is_dir = (dh->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
#else
struct dirent *de = readdir(dh->dir);
if (!de) return NWEDAV_ERROR;
strncpy(entry->name, de->d_name, sizeof(entry->name) - 1);
entry->name[sizeof(entry->name) - 1] = '\0';
entry->is_dir = (de->d_type == DT_DIR);
#endif
return NWEDAV_OK;
}
int path_normalize(const char *input, char *output, size_t output_size) {
if (!input || !output || output_size == 0) {
return NWEDAV_ERROR;
}
char *parts[256];
int num_parts = 0;
char temp[PATH_MAX];
strncpy(temp, input, sizeof(temp) - 1);
temp[sizeof(temp) - 1] = '\0';
for (size_t i = 0; temp[i]; i++) {
if (temp[i] == '\\') temp[i] = '/';
}
char *token = strtok(temp, "/");
while (token && num_parts < 256) {
if (strcmp(token, ".") == 0) {
} else if (strcmp(token, "..") == 0) {
if (num_parts > 0) {
num_parts--;
}
} else if (token[0] != '\0') {
parts[num_parts++] = token;
}
token = strtok(NULL, "/");
}
output[0] = '\0';
size_t pos = 0;
if (input[0] == '/' || input[0] == '\\') {
output[pos++] = PATH_SEPARATOR;
output[pos] = '\0';
}
for (int i = 0; i < num_parts && pos < output_size - 1; i++) {
if (i > 0 || (input[0] == '/' || input[0] == '\\')) {
if (i > 0) {
output[pos++] = PATH_SEPARATOR;
output[pos] = '\0';
}
}
size_t len = strlen(parts[i]);
if (pos + len >= output_size) break;
strcpy(output + pos, parts[i]);
pos += len;
}
return NWEDAV_OK;
}
int path_join(char *output, size_t output_size, const char *base, const char *path) {
if (!output || output_size == 0) return NWEDAV_ERROR;
if (!base || base[0] == '\0') {
strncpy(output, path ? path : "", output_size - 1);
output[output_size - 1] = '\0';
return NWEDAV_OK;
}
if (!path || path[0] == '\0') {
strncpy(output, base, output_size - 1);
output[output_size - 1] = '\0';
return NWEDAV_OK;
}
size_t base_len = strlen(base);
int has_sep = (base[base_len - 1] == '/' || base[base_len - 1] == '\\');
int path_has_sep = (path[0] == '/' || path[0] == '\\');
if (has_sep && path_has_sep) {
snprintf(output, output_size, "%s%s", base, path + 1);
} else if (has_sep || path_has_sep) {
snprintf(output, output_size, "%s%s", base, path);
} else {
snprintf(output, output_size, "%s%c%s", base, PATH_SEPARATOR, path);
}
return NWEDAV_OK;
}
int path_is_safe(const char *path, const char *root) {
if (!path || !root) return 0;
char norm_path[PATH_MAX];
char norm_root[PATH_MAX];
path_normalize(path, norm_path, sizeof(norm_path));
path_normalize(root, norm_root, sizeof(norm_root));
size_t root_len = strlen(norm_root);
if (strncmp(norm_path, norm_root, root_len) != 0) {
return 0;
}
if (norm_path[root_len] != '\0' &&
norm_path[root_len] != '/' &&
norm_path[root_len] != '\\') {
return 0;
}
return 1;
}
const char *path_basename(const char *path) {
if (!path) return NULL;
const char *last_sep = strrchr(path, PATH_SEPARATOR);
#ifdef _WIN32
const char *last_fwd = strrchr(path, '/');
if (last_fwd && (!last_sep || last_fwd > last_sep)) {
last_sep = last_fwd;
}
#endif
return last_sep ? last_sep + 1 : path;
}
const char *path_extension(const char *path) {
const char *basename = path_basename(path);
if (!basename) return NULL;
const char *dot = strrchr(basename, '.');
return dot ? dot + 1 : NULL;
}
int path_dirname(const char *path, char *output, size_t output_size) {
if (!path || !output || output_size == 0) return NWEDAV_ERROR;
const char *last_sep = strrchr(path, PATH_SEPARATOR);
#ifdef _WIN32
const char *last_fwd = strrchr(path, '/');
if (last_fwd && (!last_sep || last_fwd > last_sep)) {
last_sep = last_fwd;
}
#endif
if (!last_sep) {
output[0] = '.';
output[1] = '\0';
return NWEDAV_OK;
}
size_t len = last_sep - path;
if (len == 0) len = 1;
if (len >= output_size) {
len = output_size - 1;
}
memcpy(output, path, len);
output[len] = '\0';
return NWEDAV_OK;
}
+76
View File
@@ -0,0 +1,76 @@
#ifndef FILE_H
#define FILE_H
#include "nwedav.h"
typedef struct {
#ifdef _WIN32
HANDLE handle;
#else
int fd;
#endif
} file_handle_t;
typedef struct {
int64_t size;
int64_t mtime;
int64_t ctime;
int is_dir;
int is_file;
int is_link;
uint32_t mode;
} file_stat_t;
typedef struct {
#ifdef _WIN32
HANDLE handle;
WIN32_FIND_DATAW data;
int first;
#else
DIR *dir;
#endif
} dir_handle_t;
typedef struct {
char name[256];
int is_dir;
} dir_entry_t;
#define FILE_READ 0x01
#define FILE_WRITE 0x02
#define FILE_CREATE 0x04
#define FILE_TRUNC 0x08
#define FILE_APPEND 0x10
file_handle_t file_open(const char *path, int flags);
int file_close(file_handle_t fh);
ssize_t file_read(file_handle_t fh, void *buf, size_t count);
ssize_t file_write(file_handle_t fh, const void *buf, size_t count);
int64_t file_seek(file_handle_t fh, int64_t offset, int whence);
int64_t file_tell(file_handle_t fh);
int file_stat(const char *path, file_stat_t *st);
int file_fstat(file_handle_t fh, file_stat_t *st);
int file_exists(const char *path);
int file_is_dir(const char *path);
int file_mkdir(const char *path);
int file_mkdir_recursive(const char *path);
int file_rmdir(const char *path);
int file_unlink(const char *path);
int file_rename(const char *oldpath, const char *newpath);
int file_copy(const char *src, const char *dst);
int file_copy_recursive(const char *src, const char *dst);
int file_delete_recursive(const char *path);
int64_t file_size(const char *path);
dir_handle_t *dir_open(const char *path);
int dir_close(dir_handle_t *dh);
int dir_read(dir_handle_t *dh, dir_entry_t *entry);
int path_normalize(const char *input, char *output, size_t output_size);
int path_join(char *output, size_t output_size, const char *base, const char *path);
int path_is_safe(const char *path, const char *root);
const char *path_basename(const char *path);
const char *path_extension(const char *path);
int path_dirname(const char *path, char *output, size_t output_size);
#endif
+302
View File
@@ -0,0 +1,302 @@
#include "memory.h"
#include <string.h>
#include <ctype.h>
struct memory_pool {
void **free_list;
size_t block_size;
size_t capacity;
size_t free_count;
mutex_t mutex;
};
memory_pool_t *mempool_create(size_t block_size, size_t initial_count) {
memory_pool_t *pool = calloc(1, sizeof(memory_pool_t));
if (!pool) return NULL;
pool->block_size = block_size;
pool->capacity = initial_count;
pool->free_count = initial_count;
pool->free_list = calloc(initial_count, sizeof(void *));
if (!pool->free_list) {
free(pool);
return NULL;
}
for (size_t i = 0; i < initial_count; i++) {
pool->free_list[i] = malloc(block_size);
if (!pool->free_list[i]) {
for (size_t j = 0; j < i; j++) {
free(pool->free_list[j]);
}
free(pool->free_list);
free(pool);
return NULL;
}
}
pal_mutex_init(&pool->mutex);
return pool;
}
void mempool_destroy(memory_pool_t *pool) {
if (!pool) return;
pal_mutex_lock(&pool->mutex);
for (size_t i = 0; i < pool->free_count; i++) {
free(pool->free_list[i]);
}
free(pool->free_list);
pal_mutex_unlock(&pool->mutex);
pal_mutex_destroy(&pool->mutex);
free(pool);
}
void *mempool_alloc(memory_pool_t *pool) {
if (!pool) return malloc(64);
pal_mutex_lock(&pool->mutex);
void *ptr;
if (pool->free_count > 0) {
ptr = pool->free_list[--pool->free_count];
} else {
ptr = malloc(pool->block_size);
}
pal_mutex_unlock(&pool->mutex);
return ptr;
}
void mempool_free(memory_pool_t *pool, void *ptr) {
if (!ptr) return;
if (!pool) {
free(ptr);
return;
}
pal_mutex_lock(&pool->mutex);
if (pool->free_count < pool->capacity) {
pool->free_list[pool->free_count++] = ptr;
} else {
void **new_list = realloc(pool->free_list, (pool->capacity * 2) * sizeof(void *));
if (new_list) {
pool->free_list = new_list;
pool->capacity *= 2;
pool->free_list[pool->free_count++] = ptr;
} else {
free(ptr);
}
}
pal_mutex_unlock(&pool->mutex);
}
size_t mempool_available(memory_pool_t *pool) {
if (!pool) return 0;
pal_mutex_lock(&pool->mutex);
size_t count = pool->free_count;
pal_mutex_unlock(&pool->mutex);
return count;
}
buffer_t *buffer_create(size_t initial_capacity) {
buffer_t *buf = calloc(1, sizeof(buffer_t));
if (!buf) return NULL;
if (initial_capacity > 0) {
buf->data = malloc(initial_capacity);
if (!buf->data) {
free(buf);
return NULL;
}
buf->capacity = initial_capacity;
buf->data[0] = '\0';
}
return buf;
}
void buffer_destroy(buffer_t *buf) {
if (!buf) return;
free(buf->data);
free(buf);
}
int buffer_reserve(buffer_t *buf, size_t capacity) {
if (!buf) return NWEDAV_ERROR;
if (buf->capacity >= capacity) return NWEDAV_OK;
size_t new_cap = buf->capacity ? buf->capacity : 64;
while (new_cap < capacity) {
new_cap *= 2;
}
char *new_data = realloc(buf->data, new_cap);
if (!new_data) return NWEDAV_ERROR_MEMORY;
buf->data = new_data;
buf->capacity = new_cap;
return NWEDAV_OK;
}
int buffer_append(buffer_t *buf, const void *data, size_t len) {
if (!buf || !data || len == 0) return NWEDAV_OK;
if (buffer_reserve(buf, buf->size + len + 1) != NWEDAV_OK) {
return NWEDAV_ERROR_MEMORY;
}
memcpy(buf->data + buf->size, data, len);
buf->size += len;
buf->data[buf->size] = '\0';
return NWEDAV_OK;
}
int buffer_append_str(buffer_t *buf, const char *str) {
if (!str) return NWEDAV_OK;
return buffer_append(buf, str, strlen(str));
}
int buffer_append_fmt(buffer_t *buf, const char *fmt, ...) {
if (!buf || !fmt) return NWEDAV_ERROR;
va_list ap;
va_start(ap, fmt);
int needed = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (needed < 0) return NWEDAV_ERROR;
if (buffer_reserve(buf, buf->size + needed + 1) != NWEDAV_OK) {
return NWEDAV_ERROR_MEMORY;
}
va_start(ap, fmt);
vsnprintf(buf->data + buf->size, needed + 1, fmt, ap);
va_end(ap);
buf->size += needed;
return NWEDAV_OK;
}
void buffer_consume(buffer_t *buf, size_t len) {
if (!buf || len == 0) return;
if (len >= buf->size) {
buf->size = 0;
if (buf->data) buf->data[0] = '\0';
return;
}
memmove(buf->data, buf->data + len, buf->size - len);
buf->size -= len;
buf->data[buf->size] = '\0';
}
void buffer_reset(buffer_t *buf) {
if (!buf) return;
buf->size = 0;
if (buf->data) buf->data[0] = '\0';
}
char *buffer_detach(buffer_t *buf) {
if (!buf) return NULL;
char *data = buf->data;
buf->data = NULL;
buf->size = 0;
buf->capacity = 0;
return data;
}
char *str_dup(const char *str) {
if (!str) return NULL;
size_t len = strlen(str);
char *dup = malloc(len + 1);
if (dup) {
memcpy(dup, str, len + 1);
}
return dup;
}
char *str_ndup(const char *str, size_t n) {
if (!str) return NULL;
size_t len = strlen(str);
if (n < len) len = n;
char *dup = malloc(len + 1);
if (dup) {
memcpy(dup, str, len);
dup[len] = '\0';
}
return dup;
}
char *str_lower(char *str) {
if (!str) return NULL;
for (char *p = str; *p; p++) {
*p = tolower((unsigned char)*p);
}
return str;
}
char *str_upper(char *str) {
if (!str) return NULL;
for (char *p = str; *p; p++) {
*p = toupper((unsigned char)*p);
}
return str;
}
char *str_trim(char *str) {
if (!str) return NULL;
while (isspace((unsigned char)*str)) str++;
if (*str == '\0') return str;
char *end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
end[1] = '\0';
return str;
}
int str_starts_with(const char *str, const char *prefix) {
if (!str || !prefix) return 0;
size_t prefix_len = strlen(prefix);
return strncmp(str, prefix, prefix_len) == 0;
}
int str_ends_with(const char *str, const char *suffix) {
if (!str || !suffix) return 0;
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
if (suffix_len > str_len) return 0;
return strcmp(str + str_len - suffix_len, suffix) == 0;
}
int str_casecmp(const char *a, const char *b) {
if (!a && !b) return 0;
if (!a) return -1;
if (!b) return 1;
#ifdef _WIN32
return _stricmp(a, b);
#else
return strcasecmp(a, b);
#endif
}
int str_ncasecmp(const char *a, const char *b, size_t n) {
if (!a && !b) return 0;
if (!a) return -1;
if (!b) return 1;
#ifdef _WIN32
return _strnicmp(a, b, n);
#else
return strncasecmp(a, b, n);
#endif
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef MEMORY_H
#define MEMORY_H
#include "nwedav.h"
#include "pal.h"
typedef struct memory_pool memory_pool_t;
typedef struct buffer {
char *data;
size_t size;
size_t capacity;
} buffer_t;
memory_pool_t *mempool_create(size_t block_size, size_t initial_count);
void mempool_destroy(memory_pool_t *pool);
void *mempool_alloc(memory_pool_t *pool);
void mempool_free(memory_pool_t *pool, void *ptr);
size_t mempool_available(memory_pool_t *pool);
buffer_t *buffer_create(size_t initial_capacity);
void buffer_destroy(buffer_t *buf);
int buffer_reserve(buffer_t *buf, size_t capacity);
int buffer_append(buffer_t *buf, const void *data, size_t len);
int buffer_append_str(buffer_t *buf, const char *str);
int buffer_append_fmt(buffer_t *buf, const char *fmt, ...);
void buffer_consume(buffer_t *buf, size_t len);
void buffer_reset(buffer_t *buf);
char *buffer_detach(buffer_t *buf);
char *str_dup(const char *str);
char *str_ndup(const char *str, size_t n);
char *str_lower(char *str);
char *str_upper(char *str);
char *str_trim(char *str);
int str_starts_with(const char *str, const char *prefix);
int str_ends_with(const char *str, const char *suffix);
int str_casecmp(const char *a, const char *b);
int str_ncasecmp(const char *a, const char *b, size_t n);
#endif
+393
View File
@@ -0,0 +1,393 @@
#include "pal.h"
#ifdef _WIN32
#include <mswsock.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "mswsock.lib")
#else
#include <sys/time.h>
#include <signal.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#elif defined(__APPLE__)
#include <sys/sysctl.h>
#endif
#endif
int pal_init(void) {
#ifdef _WIN32
WSADATA wsa_data;
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
return NWEDAV_ERROR_NETWORK;
}
#else
signal(SIGPIPE, SIG_IGN);
#endif
return NWEDAV_OK;
}
void pal_cleanup(void) {
#ifdef _WIN32
WSACleanup();
#endif
}
socket_t pal_socket_create(int domain, int type, int protocol) {
#ifdef _WIN32
return WSASocket(domain, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
return socket(domain, type, protocol);
#endif
}
int pal_socket_close(socket_t sock) {
#ifdef _WIN32
return closesocket(sock);
#else
return close(sock);
#endif
}
int pal_socket_bind(socket_t sock, const struct sockaddr *addr, socklen_t len) {
return bind(sock, addr, len);
}
int pal_socket_listen(socket_t sock, int backlog) {
return listen(sock, backlog);
}
socket_t pal_socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *len) {
return accept(sock, addr, len);
}
int pal_socket_connect(socket_t sock, const struct sockaddr *addr, socklen_t len) {
return connect(sock, addr, len);
}
ssize_t pal_socket_send(socket_t sock, const void *buf, size_t len, int flags) {
#ifdef _WIN32
return send(sock, (const char *)buf, (int)len, flags);
#else
return send(sock, buf, len, flags);
#endif
}
ssize_t pal_socket_recv(socket_t sock, void *buf, size_t len, int flags) {
#ifdef _WIN32
return recv(sock, (char *)buf, (int)len, flags);
#else
return recv(sock, buf, len, flags);
#endif
}
int pal_socket_setnonblock(socket_t sock, int nonblock) {
#ifdef _WIN32
u_long mode = nonblock ? 1 : 0;
return ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) return -1;
if (nonblock) {
flags |= O_NONBLOCK;
} else {
flags &= ~O_NONBLOCK;
}
return fcntl(sock, F_SETFL, flags);
#endif
}
int pal_socket_setreuseaddr(socket_t sock, int reuse) {
int opt = reuse ? 1 : 0;
#ifdef _WIN32
return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
#else
return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
}
int pal_socket_setnodelay(socket_t sock, int nodelay) {
int opt = nodelay ? 1 : 0;
#ifdef _WIN32
return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
#else
#include <netinet/tcp.h>
return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
#endif
}
int pal_socket_shutdown(socket_t sock, int how) {
return shutdown(sock, how);
}
int pal_socket_error(void) {
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
#ifdef _WIN32
typedef struct {
void *(*func)(void *);
void *arg;
} win_thread_param_t;
static DWORD WINAPI win_thread_wrapper(LPVOID param) {
win_thread_param_t *p = (win_thread_param_t *)param;
void *(*func)(void *) = p->func;
void *arg = p->arg;
free(p);
func(arg);
return 0;
}
#endif
int pal_thread_create(thread_t *t, void *(*func)(void *), void *arg) {
#ifdef _WIN32
win_thread_param_t *param = malloc(sizeof(win_thread_param_t));
if (!param) return NWEDAV_ERROR_MEMORY;
param->func = func;
param->arg = arg;
*t = CreateThread(NULL, 0, win_thread_wrapper, param, 0, NULL);
if (*t == NULL) {
free(param);
return NWEDAV_ERROR;
}
return NWEDAV_OK;
#else
return pthread_create(t, NULL, func, arg) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_thread_join(thread_t t, void **retval) {
#ifdef _WIN32
(void)retval;
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
return NWEDAV_OK;
#else
return pthread_join(t, retval) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
void pal_thread_detach(thread_t t) {
#ifdef _WIN32
CloseHandle(t);
#else
pthread_detach(t);
#endif
}
void pal_thread_exit(void *retval) {
#ifdef _WIN32
(void)retval;
ExitThread(0);
#else
pthread_exit(retval);
#endif
}
int pal_mutex_init(mutex_t *m) {
#ifdef _WIN32
InitializeCriticalSection(m);
return NWEDAV_OK;
#else
return pthread_mutex_init(m, NULL) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_mutex_destroy(mutex_t *m) {
#ifdef _WIN32
DeleteCriticalSection(m);
return NWEDAV_OK;
#else
return pthread_mutex_destroy(m) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_mutex_lock(mutex_t *m) {
#ifdef _WIN32
EnterCriticalSection(m);
return NWEDAV_OK;
#else
return pthread_mutex_lock(m) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_mutex_trylock(mutex_t *m) {
#ifdef _WIN32
return TryEnterCriticalSection(m) ? NWEDAV_OK : NWEDAV_ERROR;
#else
return pthread_mutex_trylock(m) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_mutex_unlock(mutex_t *m) {
#ifdef _WIN32
LeaveCriticalSection(m);
return NWEDAV_OK;
#else
return pthread_mutex_unlock(m) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_init(cond_t *c) {
#ifdef _WIN32
InitializeConditionVariable(c);
return NWEDAV_OK;
#else
return pthread_cond_init(c, NULL) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_destroy(cond_t *c) {
#ifdef _WIN32
(void)c;
return NWEDAV_OK;
#else
return pthread_cond_destroy(c) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_wait(cond_t *c, mutex_t *m) {
#ifdef _WIN32
return SleepConditionVariableCS(c, m, INFINITE) ? NWEDAV_OK : NWEDAV_ERROR;
#else
return pthread_cond_wait(c, m) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_timedwait(cond_t *c, mutex_t *m, int timeout_ms) {
#ifdef _WIN32
return SleepConditionVariableCS(c, m, timeout_ms) ? NWEDAV_OK : NWEDAV_ERROR;
#else
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
return pthread_cond_timedwait(c, m, &ts) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_signal(cond_t *c) {
#ifdef _WIN32
WakeConditionVariable(c);
return NWEDAV_OK;
#else
return pthread_cond_signal(c) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_cond_broadcast(cond_t *c) {
#ifdef _WIN32
WakeAllConditionVariable(c);
return NWEDAV_OK;
#else
return pthread_cond_broadcast(c) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_rwlock_init(rwlock_t *rw) {
#ifdef _WIN32
InitializeSRWLock(rw);
return NWEDAV_OK;
#else
return pthread_rwlock_init(rw, NULL) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_rwlock_destroy(rwlock_t *rw) {
#ifdef _WIN32
(void)rw;
return NWEDAV_OK;
#else
return pthread_rwlock_destroy(rw) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_rwlock_rdlock(rwlock_t *rw) {
#ifdef _WIN32
AcquireSRWLockShared(rw);
return NWEDAV_OK;
#else
return pthread_rwlock_rdlock(rw) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_rwlock_wrlock(rwlock_t *rw) {
#ifdef _WIN32
AcquireSRWLockExclusive(rw);
return NWEDAV_OK;
#else
return pthread_rwlock_wrlock(rw) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int pal_rwlock_unlock(rwlock_t *rw) {
#ifdef _WIN32
ReleaseSRWLockShared(rw);
return NWEDAV_OK;
#else
return pthread_rwlock_unlock(rw) == 0 ? NWEDAV_OK : NWEDAV_ERROR;
#endif
}
int64_t pal_time_now(void) {
#ifdef _WIN32
FILETIME ft;
ULARGE_INTEGER li;
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
return (int64_t)((li.QuadPart - 116444736000000000ULL) / 10000000ULL);
#else
return (int64_t)time(NULL);
#endif
}
int64_t pal_time_monotonic(void) {
#ifdef _WIN32
return (int64_t)GetTickCount64();
#elif defined(__APPLE__)
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
#endif
}
void pal_sleep_ms(int ms) {
#ifdef _WIN32
Sleep(ms);
#else
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
#endif
}
int pal_get_cpu_count(void) {
#ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return (int)si.dwNumberOfProcessors;
#elif defined(__linux__)
return get_nprocs();
#elif defined(__APPLE__)
int count;
size_t len = sizeof(count);
sysctlbyname("hw.ncpu", &count, &len, NULL, 0);
return count;
#else
return 1;
#endif
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef PAL_H
#define PAL_H
#include "nwedav.h"
int pal_init(void);
void pal_cleanup(void);
socket_t pal_socket_create(int domain, int type, int protocol);
int pal_socket_close(socket_t sock);
int pal_socket_bind(socket_t sock, const struct sockaddr *addr, socklen_t len);
int pal_socket_listen(socket_t sock, int backlog);
socket_t pal_socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *len);
int pal_socket_connect(socket_t sock, const struct sockaddr *addr, socklen_t len);
ssize_t pal_socket_send(socket_t sock, const void *buf, size_t len, int flags);
ssize_t pal_socket_recv(socket_t sock, void *buf, size_t len, int flags);
int pal_socket_setnonblock(socket_t sock, int nonblock);
int pal_socket_setreuseaddr(socket_t sock, int reuse);
int pal_socket_setnodelay(socket_t sock, int nodelay);
int pal_socket_shutdown(socket_t sock, int how);
int pal_socket_error(void);
int pal_thread_create(thread_t *t, void *(*func)(void *), void *arg);
int pal_thread_join(thread_t t, void **retval);
void pal_thread_detach(thread_t t);
void pal_thread_exit(void *retval);
int pal_mutex_init(mutex_t *m);
int pal_mutex_destroy(mutex_t *m);
int pal_mutex_lock(mutex_t *m);
int pal_mutex_trylock(mutex_t *m);
int pal_mutex_unlock(mutex_t *m);
int pal_cond_init(cond_t *c);
int pal_cond_destroy(cond_t *c);
int pal_cond_wait(cond_t *c, mutex_t *m);
int pal_cond_timedwait(cond_t *c, mutex_t *m, int timeout_ms);
int pal_cond_signal(cond_t *c);
int pal_cond_broadcast(cond_t *c);
int pal_rwlock_init(rwlock_t *rw);
int pal_rwlock_destroy(rwlock_t *rw);
int pal_rwlock_rdlock(rwlock_t *rw);
int pal_rwlock_wrlock(rwlock_t *rw);
int pal_rwlock_unlock(rwlock_t *rw);
int64_t pal_time_now(void);
int64_t pal_time_monotonic(void);
void pal_sleep_ms(int ms);
int pal_get_cpu_count(void);
#endif
+131
View File
@@ -0,0 +1,131 @@
#include "time.h"
#include <string.h>
#include <ctype.h>
static const char *day_names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int time_format_http(int64_t timestamp, char *buf, size_t len) {
if (!buf || len < 30) return NWEDAV_ERROR;
time_t t = (time_t)timestamp;
struct tm *tm = gmtime(&t);
if (!tm) return NWEDAV_ERROR;
snprintf(buf, len, "%s, %02d %s %04d %02d:%02d:%02d GMT",
day_names[tm->tm_wday],
tm->tm_mday,
month_names[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
return NWEDAV_OK;
}
int time_format_iso8601(int64_t timestamp, char *buf, size_t len) {
if (!buf || len < 25) return NWEDAV_ERROR;
time_t t = (time_t)timestamp;
struct tm *tm = gmtime(&t);
if (!tm) return NWEDAV_ERROR;
snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d:%02dZ",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
return NWEDAV_OK;
}
int time_format_rfc3339(int64_t timestamp, char *buf, size_t len) {
return time_format_iso8601(timestamp, buf, len);
}
static int parse_month(const char *str) {
for (int i = 0; i < 12; i++) {
if (strncasecmp(str, month_names[i], 3) == 0) {
return i;
}
}
return -1;
}
int64_t time_parse_http(const char *str) {
if (!str) return 0;
struct tm tm;
memset(&tm, 0, sizeof(tm));
while (*str && !isalpha(*str)) str++;
while (*str && isalpha(*str)) str++;
while (*str && *str == ',') str++;
while (*str && *str == ' ') str++;
int day, year, hour, min, sec;
char month[4] = {0};
if (sscanf(str, "%d %3s %d %d:%d:%d", &day, month, &year, &hour, &min, &sec) == 6) {
tm.tm_mday = day;
tm.tm_mon = parse_month(month);
tm.tm_year = year - 1900;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
} else if (sscanf(str, "%d-%3s-%d %d:%d:%d", &day, month, &year, &hour, &min, &sec) == 6) {
tm.tm_mday = day;
tm.tm_mon = parse_month(month);
tm.tm_year = (year < 100) ? year + 100 : year - 1900;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
} else {
return 0;
}
if (tm.tm_mon < 0) return 0;
#ifdef _WIN32
return (int64_t)_mkgmtime(&tm);
#else
return (int64_t)timegm(&tm);
#endif
}
int64_t time_parse_iso8601(const char *str) {
if (!str) return 0;
struct tm tm;
memset(&tm, 0, sizeof(tm));
int year, mon, day, hour = 0, min = 0, sec = 0;
if (sscanf(str, "%d-%d-%dT%d:%d:%d", &year, &mon, &day, &hour, &min, &sec) >= 3) {
tm.tm_year = year - 1900;
tm.tm_mon = mon - 1;
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
} else {
return 0;
}
#ifdef _WIN32
return (int64_t)_mkgmtime(&tm);
#else
return (int64_t)timegm(&tm);
#endif
}
char *time_format_etag(int64_t mtime, int64_t size) {
char *etag = malloc(64);
if (!etag) return NULL;
snprintf(etag, 64, "\"%llx-%llx\"", (unsigned long long)mtime, (unsigned long long)size);
return etag;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef TIME_H
#define TIME_H
#include "nwedav.h"
int time_format_http(int64_t timestamp, char *buf, size_t len);
int time_format_iso8601(int64_t timestamp, char *buf, size_t len);
int time_format_rfc3339(int64_t timestamp, char *buf, size_t len);
int64_t time_parse_http(const char *str);
int64_t time_parse_iso8601(const char *str);
char *time_format_etag(int64_t mtime, int64_t size);
#endif
+434
View File
@@ -0,0 +1,434 @@
#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_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);
const char *name = path_basename(full_path);
if (name) {
strncpy(info->name, name, sizeof(info->name) - 1);
}
info->is_collection = st.is_dir;
info->size = st.size;
info->mtime = st.mtime;
info->ctime = st.ctime;
if (st.is_dir) {
strcpy(info->content_type, "httpd/unix-directory");
} else {
const char *mime = http_get_mime_type_for_file(info->name);
strncpy(info->content_type, mime, sizeof(info->content_type) - 1);
}
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_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;
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef STORAGE_BACKEND_H
#define STORAGE_BACKEND_H
#include "nwedav.h"
#include "../platform/file.h"
typedef struct {
char path[PATH_MAX];
char name[256];
int is_collection;
int64_t size;
int64_t mtime;
int64_t ctime;
char content_type[128];
char etag[64];
} resource_info_t;
typedef struct storage_backend storage_backend_t;
storage_backend_t *storage_create(const char *root_path);
void storage_destroy(storage_backend_t *storage);
int storage_get_info(storage_backend_t *storage, const char *path, resource_info_t *info);
int storage_exists(storage_backend_t *storage, const char *path);
int storage_is_collection(storage_backend_t *storage, const char *path);
int storage_read_file(storage_backend_t *storage, const char *path,
char **data, size_t *len);
int storage_write_file(storage_backend_t *storage, const char *path,
const char *data, size_t len);
int storage_delete(storage_backend_t *storage, const char *path);
int storage_create_collection(storage_backend_t *storage, const char *path);
int storage_copy(storage_backend_t *storage, const char *src, const char *dst, int overwrite);
int storage_move(storage_backend_t *storage, const char *src, const char *dst, int overwrite);
typedef int (*storage_list_callback_t)(const char *path, resource_info_t *info, void *user_data);
int storage_list(storage_backend_t *storage, const char *path, int depth,
storage_list_callback_t callback, void *user_data);
const char *storage_get_root(storage_backend_t *storage);
int storage_resolve_path(storage_backend_t *storage, const char *uri, char *path, size_t path_size);
int storage_uri_from_path(storage_backend_t *storage, const char *path, char *uri, size_t uri_size);
int64_t storage_calculate_size(storage_backend_t *storage, const char *path);
#endif
+419
View File
@@ -0,0 +1,419 @@
#include "locks.h"
#include "../platform/memory.h"
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <rpc.h>
#else
#include <uuid/uuid.h>
#endif
struct lock_manager {
sqlite3 *db;
mutex_t mutex;
};
static int init_database(sqlite3 *db) {
const char *sql =
"CREATE TABLE IF NOT EXISTS locks ("
" token TEXT PRIMARY KEY,"
" resource_path TEXT NOT NULL,"
" owner TEXT,"
" scope INTEGER NOT NULL,"
" type INTEGER NOT NULL,"
" depth INTEGER NOT NULL,"
" created INTEGER NOT NULL,"
" timeout INTEGER NOT NULL,"
" user_id INTEGER NOT NULL"
");"
"CREATE INDEX IF NOT EXISTS idx_locks_path ON locks(resource_path);"
"CREATE INDEX IF NOT EXISTS idx_locks_timeout ON locks(timeout);";
char *err = NULL;
int ret = sqlite3_exec(db, sql, NULL, NULL, &err);
if (ret != SQLITE_OK) {
sqlite3_free(err);
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
lock_manager_t *lock_manager_create(const char *db_path) {
lock_manager_t *mgr = calloc(1, sizeof(lock_manager_t));
if (!mgr) return NULL;
if (pal_mutex_init(&mgr->mutex) != NWEDAV_OK) {
free(mgr);
return NULL;
}
int ret = sqlite3_open(db_path, &mgr->db);
if (ret != SQLITE_OK) {
pal_mutex_destroy(&mgr->mutex);
free(mgr);
return NULL;
}
sqlite3_busy_timeout(mgr->db, 5000);
if (init_database(mgr->db) != NWEDAV_OK) {
sqlite3_close(mgr->db);
pal_mutex_destroy(&mgr->mutex);
free(mgr);
return NULL;
}
return mgr;
}
void lock_manager_destroy(lock_manager_t *mgr) {
if (!mgr) return;
pal_mutex_lock(&mgr->mutex);
sqlite3_close(mgr->db);
pal_mutex_unlock(&mgr->mutex);
pal_mutex_destroy(&mgr->mutex);
free(mgr);
}
char *lock_generate_token(void) {
char *token = malloc(64);
if (!token) return NULL;
#ifdef _WIN32
UUID uuid;
UuidCreate(&uuid);
unsigned char *str;
UuidToStringA(&uuid, &str);
snprintf(token, 64, "opaquelocktoken:%s", str);
RpcStringFreeA(&str);
#else
uuid_t uuid;
uuid_generate(uuid);
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
snprintf(token, 64, "opaquelocktoken:%s", uuid_str);
#endif
return token;
}
int lock_manager_create_lock(lock_manager_t *mgr, lock_entry_t *lock) {
if (!mgr || !lock) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
if (lock->scope == LOCK_SCOPE_EXCLUSIVE) {
sqlite3_stmt *check;
const char *check_sql =
"SELECT COUNT(*) FROM locks WHERE "
"(resource_path = ? OR ? LIKE resource_path || '/%' OR resource_path LIKE ? || '/%') "
"AND timeout > ?";
int ret = sqlite3_prepare_v2(mgr->db, check_sql, -1, &check, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(check, 1, lock->resource_path, -1, SQLITE_STATIC);
sqlite3_bind_text(check, 2, lock->resource_path, -1, SQLITE_STATIC);
sqlite3_bind_text(check, 3, lock->resource_path, -1, SQLITE_STATIC);
sqlite3_bind_int64(check, 4, time(NULL));
if (sqlite3_step(check) == SQLITE_ROW) {
int count = sqlite3_column_int(check, 0);
sqlite3_finalize(check);
if (count > 0) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_LOCKED;
}
} else {
sqlite3_finalize(check);
}
}
char *token = lock_generate_token();
if (!token) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_MEMORY;
}
strncpy(lock->token, token, sizeof(lock->token) - 1);
free(token);
lock->created = time(NULL);
if (lock->timeout <= 0) {
lock->timeout = lock->created + 3600;
} else {
lock->timeout = lock->created + lock->timeout;
}
sqlite3_stmt *stmt;
const char *sql =
"INSERT INTO locks (token, resource_path, owner, scope, type, depth, created, timeout, user_id) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, lock->token, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, lock->resource_path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, lock->owner, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, lock->scope);
sqlite3_bind_int(stmt, 5, lock->type);
sqlite3_bind_int(stmt, 6, lock->depth);
sqlite3_bind_int64(stmt, 7, lock->created);
sqlite3_bind_int64(stmt, 8, lock->timeout);
sqlite3_bind_int(stmt, 9, lock->user_id);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int lock_manager_refresh_lock(lock_manager_t *mgr, const char *token, int timeout) {
if (!mgr || !token) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE locks SET timeout = ? WHERE token = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
int64_t new_timeout = time(NULL) + (timeout > 0 ? timeout : 3600);
sqlite3_bind_int64(stmt, 1, new_timeout);
sqlite3_bind_text(stmt, 2, token, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
if (ret == SQLITE_DONE && changes > 0) {
return NWEDAV_OK;
}
return NWEDAV_ERROR_NOT_FOUND;
}
int lock_manager_remove_lock(lock_manager_t *mgr, const char *token) {
if (!mgr || !token) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM locks WHERE token = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, token, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
if (ret == SQLITE_DONE && changes > 0) {
return NWEDAV_OK;
}
return NWEDAV_ERROR_NOT_FOUND;
}
int lock_manager_get_lock(lock_manager_t *mgr, const char *token, lock_entry_t *lock) {
if (!mgr || !token || !lock) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT token, resource_path, owner, scope, type, depth, created, timeout, user_id "
"FROM locks WHERE token = ? AND timeout > ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, token, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, time(NULL));
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
strncpy(lock->token, (const char *)sqlite3_column_text(stmt, 0), sizeof(lock->token) - 1);
strncpy(lock->resource_path, (const char *)sqlite3_column_text(stmt, 1), sizeof(lock->resource_path) - 1);
const char *owner = (const char *)sqlite3_column_text(stmt, 2);
if (owner) strncpy(lock->owner, owner, sizeof(lock->owner) - 1);
lock->scope = sqlite3_column_int(stmt, 3);
lock->type = sqlite3_column_int(stmt, 4);
lock->depth = sqlite3_column_int(stmt, 5);
lock->created = sqlite3_column_int64(stmt, 6);
lock->timeout = sqlite3_column_int64(stmt, 7);
lock->user_id = sqlite3_column_int(stmt, 8);
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return ret;
}
int lock_manager_get_locks_for_path(lock_manager_t *mgr, const char *path,
lock_entry_t **locks, int *count) {
if (!mgr || !path || !locks || !count) return NWEDAV_ERROR;
*locks = NULL;
*count = 0;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT token, resource_path, owner, scope, type, depth, created, timeout, user_id "
"FROM locks WHERE "
"(resource_path = ? OR ? LIKE resource_path || '/%' OR resource_path LIKE ? || '/%') "
"AND timeout > ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, path, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 4, time(NULL));
int capacity = 8;
*locks = malloc(capacity * sizeof(lock_entry_t));
if (!*locks) {
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_MEMORY;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
if (*count >= capacity) {
capacity *= 2;
lock_entry_t *new_locks = realloc(*locks, capacity * sizeof(lock_entry_t));
if (!new_locks) {
free(*locks);
*locks = NULL;
*count = 0;
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_MEMORY;
}
*locks = new_locks;
}
lock_entry_t *lock = &(*locks)[*count];
memset(lock, 0, sizeof(lock_entry_t));
strncpy(lock->token, (const char *)sqlite3_column_text(stmt, 0), sizeof(lock->token) - 1);
strncpy(lock->resource_path, (const char *)sqlite3_column_text(stmt, 1), sizeof(lock->resource_path) - 1);
const char *owner = (const char *)sqlite3_column_text(stmt, 2);
if (owner) strncpy(lock->owner, owner, sizeof(lock->owner) - 1);
lock->scope = sqlite3_column_int(stmt, 3);
lock->type = sqlite3_column_int(stmt, 4);
lock->depth = sqlite3_column_int(stmt, 5);
lock->created = sqlite3_column_int64(stmt, 6);
lock->timeout = sqlite3_column_int64(stmt, 7);
lock->user_id = sqlite3_column_int(stmt, 8);
(*count)++;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_OK;
}
int lock_manager_is_locked(lock_manager_t *mgr, const char *path) {
if (!mgr || !path) return 0;
lock_entry_t *locks;
int count;
if (lock_manager_get_locks_for_path(mgr, path, &locks, &count) != NWEDAV_OK) {
return 0;
}
free(locks);
return count > 0;
}
int lock_manager_check_lock(lock_manager_t *mgr, const char *path,
const char *token, uint32_t user_id) {
if (!mgr || !path) return NWEDAV_ERROR;
lock_entry_t *locks;
int count;
int ret = lock_manager_get_locks_for_path(mgr, path, &locks, &count);
if (ret != NWEDAV_OK) return ret;
if (count == 0) {
free(locks);
return NWEDAV_OK;
}
for (int i = 0; i < count; i++) {
if (token && strcmp(locks[i].token, token) == 0) {
free(locks);
return NWEDAV_OK;
}
if (locks[i].user_id == user_id) {
free(locks);
return NWEDAV_OK;
}
}
free(locks);
return NWEDAV_ERROR_LOCKED;
}
int lock_manager_cleanup_expired(lock_manager_t *mgr) {
if (!mgr) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM locks WHERE timeout < ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int64(stmt, 1, time(NULL));
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef STORAGE_LOCKS_H
#define STORAGE_LOCKS_H
#include "nwedav.h"
#include "../platform/pal.h"
#include <sqlite3.h>
typedef struct lock_entry {
char token[64];
char resource_path[PATH_MAX];
char owner[512];
lock_scope_t scope;
lock_type_t type;
lock_depth_t depth;
int64_t created;
int64_t timeout;
uint32_t user_id;
} lock_entry_t;
typedef struct lock_manager lock_manager_t;
lock_manager_t *lock_manager_create(const char *db_path);
void lock_manager_destroy(lock_manager_t *mgr);
int lock_manager_create_lock(lock_manager_t *mgr, lock_entry_t *lock);
int lock_manager_refresh_lock(lock_manager_t *mgr, const char *token, int timeout);
int lock_manager_remove_lock(lock_manager_t *mgr, const char *token);
int lock_manager_get_lock(lock_manager_t *mgr, const char *token, lock_entry_t *lock);
int lock_manager_get_locks_for_path(lock_manager_t *mgr, const char *path,
lock_entry_t **locks, int *count);
int lock_manager_is_locked(lock_manager_t *mgr, const char *path);
int lock_manager_check_lock(lock_manager_t *mgr, const char *path,
const char *token, uint32_t user_id);
int lock_manager_cleanup_expired(lock_manager_t *mgr);
char *lock_generate_token(void);
#endif
+289
View File
@@ -0,0 +1,289 @@
#include "properties.h"
#include "../platform/memory.h"
#include <string.h>
struct property_store {
sqlite3 *db;
mutex_t mutex;
};
static int init_database(sqlite3 *db) {
const char *sql =
"CREATE TABLE IF NOT EXISTS properties ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" resource_path TEXT NOT NULL,"
" namespace TEXT NOT NULL,"
" name TEXT NOT NULL,"
" value TEXT,"
" UNIQUE(resource_path, namespace, name)"
");"
"CREATE INDEX IF NOT EXISTS idx_props_path ON properties(resource_path);";
char *err = NULL;
int ret = sqlite3_exec(db, sql, NULL, NULL, &err);
if (ret != SQLITE_OK) {
sqlite3_free(err);
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
property_store_t *property_store_create(const char *db_path) {
property_store_t *store = calloc(1, sizeof(property_store_t));
if (!store) return NULL;
if (pal_mutex_init(&store->mutex) != NWEDAV_OK) {
free(store);
return NULL;
}
int ret = sqlite3_open(db_path, &store->db);
if (ret != SQLITE_OK) {
pal_mutex_destroy(&store->mutex);
free(store);
return NULL;
}
sqlite3_busy_timeout(store->db, 5000);
if (init_database(store->db) != NWEDAV_OK) {
sqlite3_close(store->db);
pal_mutex_destroy(&store->mutex);
free(store);
return NULL;
}
return store;
}
void property_store_destroy(property_store_t *store) {
if (!store) return;
pal_mutex_lock(&store->mutex);
sqlite3_close(store->db);
pal_mutex_unlock(&store->mutex);
pal_mutex_destroy(&store->mutex);
free(store);
}
int property_store_get(property_store_t *store, const char *path,
const char *ns, const char *name, char **value) {
if (!store || !path || !ns || !name || !value) return NWEDAV_ERROR;
*value = NULL;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "SELECT value FROM properties WHERE resource_path = ? AND namespace = ? AND name = ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, ns, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, name, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
const char *val = (const char *)sqlite3_column_text(stmt, 0);
if (val) {
*value = str_dup(val);
}
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return ret;
}
int property_store_set(property_store_t *store, const char *path,
const char *ns, const char *name, const char *value) {
if (!store || !path || !ns || !name) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "INSERT OR REPLACE INTO properties (resource_path, namespace, name, value) VALUES (?, ?, ?, ?)";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, ns, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, value, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int property_store_remove(property_store_t *store, const char *path,
const char *ns, const char *name) {
if (!store || !path || !ns || !name) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM properties WHERE resource_path = ? AND namespace = ? AND name = ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, ns, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, name, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int property_store_remove_all(property_store_t *store, const char *path) {
if (!store || !path) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM properties WHERE resource_path = ? OR resource_path LIKE ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
char pattern[PATH_MAX];
snprintf(pattern, sizeof(pattern), "%s/%%", path);
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, pattern, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int property_store_list(property_store_t *store, const char *path,
property_callback_t callback, void *user_data) {
if (!store || !path || !callback) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "SELECT namespace, name, value FROM properties WHERE resource_path = ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC);
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *ns = (const char *)sqlite3_column_text(stmt, 0);
const char *name = (const char *)sqlite3_column_text(stmt, 1);
const char *value = (const char *)sqlite3_column_text(stmt, 2);
if (callback(ns, name, value, user_data) != 0) {
break;
}
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return NWEDAV_OK;
}
int property_store_copy(property_store_t *store, const char *src, const char *dst) {
if (!store || !src || !dst) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql =
"INSERT OR REPLACE INTO properties (resource_path, namespace, name, value) "
"SELECT ?, namespace, name, value FROM properties WHERE resource_path = ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, dst, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, src, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&store->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int property_store_move(property_store_t *store, const char *src, const char *dst) {
if (!store || !src || !dst) return NWEDAV_ERROR;
pal_mutex_lock(&store->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE properties SET resource_path = ? WHERE resource_path = ?";
int ret = sqlite3_prepare_v2(store->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&store->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, dst, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, src, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_stmt *stmt2;
const char *sql2 =
"UPDATE properties SET resource_path = ? || substr(resource_path, ?) "
"WHERE resource_path LIKE ?";
ret = sqlite3_prepare_v2(store->db, sql2, -1, &stmt2, NULL);
if (ret == SQLITE_OK) {
char pattern[PATH_MAX];
snprintf(pattern, sizeof(pattern), "%s/%%", src);
sqlite3_bind_text(stmt2, 1, dst, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt2, 2, (int)strlen(src) + 1);
sqlite3_bind_text(stmt2, 3, pattern, -1, SQLITE_STATIC);
sqlite3_step(stmt2);
sqlite3_finalize(stmt2);
}
pal_mutex_unlock(&store->mutex);
return NWEDAV_OK;
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef STORAGE_PROPERTIES_H
#define STORAGE_PROPERTIES_H
#include "nwedav.h"
#include "../platform/pal.h"
#include <sqlite3.h>
typedef struct property_store property_store_t;
typedef struct {
char *namespace;
char *name;
char *value;
} property_t;
property_store_t *property_store_create(const char *db_path);
void property_store_destroy(property_store_t *store);
int property_store_get(property_store_t *store, const char *path,
const char *ns, const char *name, char **value);
int property_store_set(property_store_t *store, const char *path,
const char *ns, const char *name, const char *value);
int property_store_remove(property_store_t *store, const char *path,
const char *ns, const char *name);
int property_store_remove_all(property_store_t *store, const char *path);
typedef int (*property_callback_t)(const char *ns, const char *name,
const char *value, void *user_data);
int property_store_list(property_store_t *store, const char *path,
property_callback_t callback, void *user_data);
int property_store_copy(property_store_t *store, const char *src, const char *dst);
int property_store_move(property_store_t *store, const char *src, const char *dst);
#endif
+174
View File
@@ -0,0 +1,174 @@
#include "password.h"
#include "../platform/memory.h"
#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <wincrypt.h>
#else
#include <openssl/sha.h>
#include <openssl/rand.h>
#endif
#define SALT_LENGTH 16
#define HASH_ITERATIONS 10000
static void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex) {
static const char hex_chars[] = "0123456789abcdef";
for (size_t i = 0; i < len; i++) {
hex[i * 2] = hex_chars[bytes[i] >> 4];
hex[i * 2 + 1] = hex_chars[bytes[i] & 0x0f];
}
hex[len * 2] = '\0';
}
static int hex_to_bytes(const char *hex, unsigned char *bytes, size_t max_len) {
size_t hex_len = strlen(hex);
if (hex_len % 2 != 0 || hex_len / 2 > max_len) return -1;
for (size_t i = 0; i < hex_len / 2; i++) {
unsigned int byte;
if (sscanf(hex + i * 2, "%2x", &byte) != 1) return -1;
bytes[i] = (unsigned char)byte;
}
return (int)(hex_len / 2);
}
static void generate_salt(unsigned char *salt, size_t len) {
#ifdef _WIN32
HCRYPTPROV hProv;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
CryptGenRandom(hProv, (DWORD)len, salt);
CryptReleaseContext(hProv, 0);
} else {
for (size_t i = 0; i < len; i++) {
salt[i] = (unsigned char)(rand() & 0xff);
}
}
#else
RAND_bytes(salt, len);
#endif
}
static void compute_hash(const char *password, const unsigned char *salt, size_t salt_len,
unsigned char *hash) {
#ifdef _WIN32
HCRYPTPROV hProv;
HCRYPTHASH hHash;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
return;
}
unsigned char temp[32];
unsigned char current[32 + SALT_LENGTH];
memcpy(current, salt, salt_len);
memcpy(current + salt_len, password, strlen(password));
size_t current_len = salt_len + strlen(password);
for (int i = 0; i < HASH_ITERATIONS; i++) {
if (CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) {
CryptHashData(hHash, current, (DWORD)current_len, 0);
DWORD hash_len = 32;
CryptGetHashParam(hHash, HP_HASHVAL, temp, &hash_len, 0);
CryptDestroyHash(hHash);
memcpy(current, temp, 32);
current_len = 32;
}
}
memcpy(hash, current, 32);
CryptReleaseContext(hProv, 0);
#else
unsigned char temp[SHA256_DIGEST_LENGTH];
unsigned char current[SHA256_DIGEST_LENGTH + SALT_LENGTH + 256];
memcpy(current, salt, salt_len);
size_t pass_len = strlen(password);
memcpy(current + salt_len, password, pass_len);
size_t current_len = salt_len + pass_len;
for (int i = 0; i < HASH_ITERATIONS; i++) {
SHA256(current, current_len, temp);
memcpy(current, temp, SHA256_DIGEST_LENGTH);
current_len = SHA256_DIGEST_LENGTH;
}
memcpy(hash, current, SHA256_DIGEST_LENGTH);
#endif
}
int password_hash(const char *password, char *hash_out, size_t hash_size) {
if (!password || !hash_out || hash_size < 100) {
return NWEDAV_ERROR;
}
unsigned char salt[SALT_LENGTH];
generate_salt(salt, SALT_LENGTH);
unsigned char hash[32];
compute_hash(password, salt, SALT_LENGTH, hash);
char salt_hex[SALT_LENGTH * 2 + 1];
char hash_hex[64 + 1];
bytes_to_hex(salt, SALT_LENGTH, salt_hex);
bytes_to_hex(hash, 32, hash_hex);
snprintf(hash_out, hash_size, "$pbkdf2-sha256$%d$%s$%s",
HASH_ITERATIONS, salt_hex, hash_hex);
return NWEDAV_OK;
}
int password_verify(const char *password, const char *hash) {
if (!password || !hash) {
return NWEDAV_ERROR;
}
if (strncmp(hash, "$pbkdf2-sha256$", 15) != 0) {
return NWEDAV_ERROR;
}
const char *p = hash + 15;
int iterations = atoi(p);
if (iterations <= 0) return NWEDAV_ERROR;
p = strchr(p, '$');
if (!p) return NWEDAV_ERROR;
p++;
const char *salt_end = strchr(p, '$');
if (!salt_end) return NWEDAV_ERROR;
size_t salt_hex_len = salt_end - p;
char salt_hex[64];
if (salt_hex_len >= sizeof(salt_hex)) return NWEDAV_ERROR;
memcpy(salt_hex, p, salt_hex_len);
salt_hex[salt_hex_len] = '\0';
p = salt_end + 1;
unsigned char salt[SALT_LENGTH];
int salt_len = hex_to_bytes(salt_hex, salt, sizeof(salt));
if (salt_len < 0) return NWEDAV_ERROR;
unsigned char stored_hash[32];
int stored_len = hex_to_bytes(p, stored_hash, sizeof(stored_hash));
if (stored_len != 32) return NWEDAV_ERROR;
unsigned char computed_hash[32];
compute_hash(password, salt, salt_len, computed_hash);
int match = 1;
for (int i = 0; i < 32; i++) {
if (computed_hash[i] != stored_hash[i]) {
match = 0;
}
}
return match ? NWEDAV_OK : NWEDAV_ERROR;
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef PASSWORD_H
#define PASSWORD_H
#include "nwedav.h"
int password_hash(const char *password, char *hash_out, size_t hash_size);
int password_verify(const char *password, const char *hash);
#endif
+618
View File
@@ -0,0 +1,618 @@
#include "usermgr.h"
#include "../platform/memory.h"
#include "password.h"
#include <string.h>
#include <time.h>
struct user_manager {
sqlite3 *db;
mutex_t mutex;
};
static int init_database(sqlite3 *db) {
const char *sql =
"CREATE TABLE IF NOT EXISTS users ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" username TEXT UNIQUE NOT NULL,"
" password_hash TEXT NOT NULL,"
" email TEXT,"
" display_name TEXT,"
" home_directory TEXT NOT NULL,"
" quota_bytes INTEGER DEFAULT 0,"
" used_bytes INTEGER DEFAULT 0,"
" enabled INTEGER DEFAULT 1,"
" created_at INTEGER NOT NULL,"
" updated_at INTEGER NOT NULL,"
" last_login INTEGER"
");"
"CREATE TABLE IF NOT EXISTS groups ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" name TEXT UNIQUE NOT NULL,"
" description TEXT"
");"
"CREATE TABLE IF NOT EXISTS user_groups ("
" user_id INTEGER NOT NULL,"
" group_id INTEGER NOT NULL,"
" PRIMARY KEY (user_id, group_id),"
" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,"
" FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE"
");"
"CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);";
char *err = NULL;
int ret = sqlite3_exec(db, sql, NULL, NULL, &err);
if (ret != SQLITE_OK) {
sqlite3_free(err);
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
user_manager_t *user_manager_create(const char *db_path) {
user_manager_t *mgr = calloc(1, sizeof(user_manager_t));
if (!mgr) return NULL;
if (pal_mutex_init(&mgr->mutex) != NWEDAV_OK) {
free(mgr);
return NULL;
}
int ret = sqlite3_open(db_path, &mgr->db);
if (ret != SQLITE_OK) {
pal_mutex_destroy(&mgr->mutex);
free(mgr);
return NULL;
}
sqlite3_busy_timeout(mgr->db, 5000);
if (init_database(mgr->db) != NWEDAV_OK) {
sqlite3_close(mgr->db);
pal_mutex_destroy(&mgr->mutex);
free(mgr);
return NULL;
}
return mgr;
}
void user_manager_destroy(user_manager_t *mgr) {
if (!mgr) return;
pal_mutex_lock(&mgr->mutex);
sqlite3_close(mgr->db);
pal_mutex_unlock(&mgr->mutex);
pal_mutex_destroy(&mgr->mutex);
free(mgr);
}
int user_manager_add(user_manager_t *mgr, const char *username, const char *password,
const char *email, uint64_t quota_bytes) {
if (!mgr || !username || !password) return NWEDAV_ERROR;
char hash[256];
if (password_hash(password, hash, sizeof(hash)) != NWEDAV_OK) {
return NWEDAV_ERROR;
}
char home_dir[PATH_MAX];
snprintf(home_dir, sizeof(home_dir), "/%s", username);
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"INSERT INTO users (username, password_hash, email, display_name, home_directory, "
"quota_bytes, used_bytes, enabled, created_at, updated_at) "
"VALUES (?, ?, ?, ?, ?, ?, 0, 1, ?, ?)";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
int64_t now = time(NULL);
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, hash, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, email ? email : "", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, username, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 5, home_dir, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 6, quota_bytes);
sqlite3_bind_int64(stmt, 7, now);
sqlite3_bind_int64(stmt, 8, now);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_delete(user_manager_t *mgr, const char *username) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM users WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE && changes > 0) ? NWEDAV_OK : NWEDAV_ERROR_NOT_FOUND;
}
int user_manager_set_password(user_manager_t *mgr, const char *username, const char *password) {
if (!mgr || !username || !password) return NWEDAV_ERROR;
char hash[256];
if (password_hash(password, hash, sizeof(hash)) != NWEDAV_OK) {
return NWEDAV_ERROR;
}
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET password_hash = ?, updated_at = ? WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, hash, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, time(NULL));
sqlite3_bind_text(stmt, 3, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE && changes > 0) ? NWEDAV_OK : NWEDAV_ERROR_NOT_FOUND;
}
int user_manager_enable(user_manager_t *mgr, const char *username, int enabled) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET enabled = ?, updated_at = ? WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int(stmt, 1, enabled ? 1 : 0);
sqlite3_bind_int64(stmt, 2, time(NULL));
sqlite3_bind_text(stmt, 3, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE && changes > 0) ? NWEDAV_OK : NWEDAV_ERROR_NOT_FOUND;
}
int user_manager_modify(user_manager_t *mgr, const char *username, user_t *user) {
if (!mgr || !username || !user) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"UPDATE users SET email = ?, display_name = ?, updated_at = ? WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, user->email, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, user->display_name, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, time(NULL));
sqlite3_bind_text(stmt, 4, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
int changes = sqlite3_changes(mgr->db);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE && changes > 0) ? NWEDAV_OK : NWEDAV_ERROR_NOT_FOUND;
}
static void fill_user_from_row(sqlite3_stmt *stmt, user_t *user) {
memset(user, 0, sizeof(user_t));
user->id = sqlite3_column_int(stmt, 0);
const char *val;
val = (const char *)sqlite3_column_text(stmt, 1);
if (val) strncpy(user->username, val, sizeof(user->username) - 1);
val = (const char *)sqlite3_column_text(stmt, 2);
if (val) strncpy(user->email, val, sizeof(user->email) - 1);
val = (const char *)sqlite3_column_text(stmt, 3);
if (val) strncpy(user->display_name, val, sizeof(user->display_name) - 1);
val = (const char *)sqlite3_column_text(stmt, 4);
if (val) strncpy(user->home_directory, val, sizeof(user->home_directory) - 1);
user->quota_bytes = sqlite3_column_int64(stmt, 5);
user->used_bytes = sqlite3_column_int64(stmt, 6);
user->enabled = sqlite3_column_int(stmt, 7);
user->created_at = sqlite3_column_int64(stmt, 8);
user->updated_at = sqlite3_column_int64(stmt, 9);
user->last_login = sqlite3_column_int64(stmt, 10);
}
int user_manager_get_by_id(user_manager_t *mgr, uint32_t id, user_t *user) {
if (!mgr || !user) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, username, email, display_name, home_directory, "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login "
"FROM users WHERE id = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int(stmt, 1, id);
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
fill_user_from_row(stmt, user);
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return ret;
}
int user_manager_get_by_username(user_manager_t *mgr, const char *username, user_t *user) {
if (!mgr || !username || !user) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, username, email, display_name, home_directory, "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login "
"FROM users WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
fill_user_from_row(stmt, user);
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return ret;
}
int user_manager_authenticate(user_manager_t *mgr, const char *username,
const char *password, user_t *user) {
if (!mgr || !username || !password || !user) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, username, email, display_name, home_directory, "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login, password_hash "
"FROM users WHERE username = ? AND enabled = 1";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
if (ret != SQLITE_ROW) {
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_AUTH;
}
fill_user_from_row(stmt, user);
const char *stored_hash = (const char *)sqlite3_column_text(stmt, 11);
int verified = password_verify(password, stored_hash);
sqlite3_finalize(stmt);
if (verified != NWEDAV_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR_AUTH;
}
sqlite3_stmt *update_stmt;
const char *update_sql = "UPDATE users SET last_login = ? WHERE id = ?";
if (sqlite3_prepare_v2(mgr->db, update_sql, -1, &update_stmt, NULL) == SQLITE_OK) {
sqlite3_bind_int64(update_stmt, 1, time(NULL));
sqlite3_bind_int(update_stmt, 2, user->id);
sqlite3_step(update_stmt);
sqlite3_finalize(update_stmt);
}
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_OK;
}
int user_manager_list(user_manager_t *mgr, user_callback_t callback, void *user_data) {
if (!mgr || !callback) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, username, email, display_name, home_directory, "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login "
"FROM users ORDER BY username";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
user_t user;
fill_user_from_row(stmt, &user);
if (callback(&user, user_data) != 0) {
break;
}
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_OK;
}
int user_manager_count(user_manager_t *mgr) {
if (!mgr) return 0;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "SELECT COUNT(*) FROM users";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return 0;
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return count;
}
int user_manager_update_quota_used(user_manager_t *mgr, uint32_t user_id, int64_t delta) {
if (!mgr) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET used_bytes = MAX(0, used_bytes + ?) WHERE id = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int64(stmt, 1, delta);
sqlite3_bind_int(stmt, 2, user_id);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_set_quota(user_manager_t *mgr, uint32_t user_id, uint64_t quota_bytes) {
if (!mgr) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET quota_bytes = ? WHERE id = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int64(stmt, 1, quota_bytes);
sqlite3_bind_int(stmt, 2, user_id);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_get_quota(user_manager_t *mgr, uint32_t user_id, uint64_t *quota, uint64_t *used) {
if (!mgr || !quota || !used) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "SELECT quota_bytes, used_bytes FROM users WHERE id = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_int(stmt, 1, user_id);
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
*quota = sqlite3_column_int64(stmt, 0);
*used = sqlite3_column_int64(stmt, 1);
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return ret;
}
int user_manager_group_add(user_manager_t *mgr, const char *name, const char *description) {
if (!mgr || !name) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "INSERT INTO groups (name, description) VALUES (?, ?)";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, description ? description : "", -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_group_delete(user_manager_t *mgr, const char *name) {
if (!mgr || !name) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM groups WHERE name = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_group_add_user(user_manager_t *mgr, const char *group, const char *username) {
if (!mgr || !group || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"INSERT INTO user_groups (user_id, group_id) "
"SELECT u.id, g.id FROM users u, groups g WHERE u.username = ? AND g.name = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, group, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_group_remove_user(user_manager_t *mgr, const char *group, const char *username) {
if (!mgr || !group || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"DELETE FROM user_groups WHERE user_id = (SELECT id FROM users WHERE username = ?) "
"AND group_id = (SELECT id FROM groups WHERE name = ?)";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, group, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
+58
View File
@@ -0,0 +1,58 @@
#ifndef USERMGR_H
#define USERMGR_H
#include "nwedav.h"
#include "../platform/pal.h"
#include <sqlite3.h>
typedef struct {
uint32_t id;
char username[64];
char email[256];
char display_name[256];
char home_directory[PATH_MAX];
uint64_t quota_bytes;
uint64_t used_bytes;
int enabled;
int64_t created_at;
int64_t updated_at;
int64_t last_login;
} user_t;
typedef struct {
uint32_t id;
char name[64];
char description[256];
} group_t;
typedef struct user_manager user_manager_t;
user_manager_t *user_manager_create(const char *db_path);
void user_manager_destroy(user_manager_t *mgr);
int user_manager_add(user_manager_t *mgr, const char *username, const char *password,
const char *email, uint64_t quota_bytes);
int user_manager_delete(user_manager_t *mgr, const char *username);
int user_manager_modify(user_manager_t *mgr, const char *username, user_t *user);
int user_manager_set_password(user_manager_t *mgr, const char *username, const char *password);
int user_manager_enable(user_manager_t *mgr, const char *username, int enabled);
int user_manager_get_by_id(user_manager_t *mgr, uint32_t id, user_t *user);
int user_manager_get_by_username(user_manager_t *mgr, const char *username, user_t *user);
int user_manager_authenticate(user_manager_t *mgr, const char *username,
const char *password, user_t *user);
typedef int (*user_callback_t)(user_t *user, void *user_data);
int user_manager_list(user_manager_t *mgr, user_callback_t callback, void *user_data);
int user_manager_count(user_manager_t *mgr);
int user_manager_group_add(user_manager_t *mgr, const char *name, const char *description);
int user_manager_group_delete(user_manager_t *mgr, const char *name);
int user_manager_group_add_user(user_manager_t *mgr, const char *group, const char *username);
int user_manager_group_remove_user(user_manager_t *mgr, const char *group, const char *username);
int user_manager_update_quota_used(user_manager_t *mgr, uint32_t user_id, int64_t delta);
int user_manager_set_quota(user_manager_t *mgr, uint32_t user_id, uint64_t quota_bytes);
int user_manager_get_quota(user_manager_t *mgr, uint32_t user_id, uint64_t *quota, uint64_t *used);
#endif
+219
View File
@@ -0,0 +1,219 @@
#include "methods.h"
#include "xml.h"
#include "../platform/memory.h"
#include <string.h>
#include <time.h>
static void build_lock_response(lock_entry_t *lock, buffer_t *buf) {
buffer_append_str(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
buffer_append_str(buf, "<D:prop xmlns:D=\"DAV:\">");
buffer_append_str(buf, "<D:lockdiscovery>");
buffer_append_str(buf, "<D:activelock>");
buffer_append_str(buf, "<D:locktype><D:write/></D:locktype>");
buffer_append_str(buf, "<D:lockscope>");
if (lock->scope == LOCK_SCOPE_EXCLUSIVE) {
buffer_append_str(buf, "<D:exclusive/>");
} else {
buffer_append_str(buf, "<D:shared/>");
}
buffer_append_str(buf, "</D:lockscope>");
buffer_append_str(buf, "<D:depth>");
buffer_append_str(buf, lock->depth == LOCK_DEPTH_INFINITY ? "infinity" : "0");
buffer_append_str(buf, "</D:depth>");
if (lock->owner[0]) {
buffer_append_fmt(buf, "<D:owner>%s</D:owner>", lock->owner);
}
buffer_append_str(buf, "<D:timeout>");
int64_t remaining = lock->timeout - time(NULL);
if (remaining > 0) {
buffer_append_fmt(buf, "Second-%lld", (long long)remaining);
} else {
buffer_append_str(buf, "Second-0");
}
buffer_append_str(buf, "</D:timeout>");
buffer_append_str(buf, "<D:locktoken>");
buffer_append_fmt(buf, "<D:href>%s</D:href>", lock->token);
buffer_append_str(buf, "</D:locktoken>");
buffer_append_str(buf, "<D:lockroot>");
buffer_append_fmt(buf, "<D:href>%s</D:href>", lock->resource_path);
buffer_append_str(buf, "</D:lockroot>");
buffer_append_str(buf, "</D:activelock>");
buffer_append_str(buf, "</D:lockdiscovery>");
buffer_append_str(buf, "</D:prop>");
}
int webdav_lock(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (req->if_header && req->lock_token) {
const char *token = req->lock_token;
if (strncmp(token, "<", 1) == 0) token++;
char clean_token[64];
strncpy(clean_token, token, sizeof(clean_token) - 1);
clean_token[sizeof(clean_token) - 1] = '\0';
char *end = strchr(clean_token, '>');
if (end) *end = '\0';
lock_entry_t lock;
int ret = lock_manager_get_lock(ctx->locks, clean_token, &lock);
if (ret == NWEDAV_OK) {
int timeout = req->timeout > 0 ? req->timeout : 3600;
ret = lock_manager_refresh_lock(ctx->locks, clean_token, timeout);
if (ret == NWEDAV_OK) {
lock.timeout = time(NULL) + timeout;
buffer_t *buf = buffer_create(1024);
build_lock_response(&lock, buf);
http_response_set_status(res, HTTP_200_OK);
http_response_add_header(res, "Content-Type", "application/xml; charset=utf-8");
http_response_add_header(res, "Lock-Token", lock.token);
http_response_set_body(res, buf->data, buf->size);
buffer_destroy(buf);
return NWEDAV_OK;
}
}
http_response_error(res, HTTP_412_PRECONDITION_FAILED, "Lock refresh failed");
return NWEDAV_OK;
}
if (!req->body || req->body_len == 0) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Request body required");
return NWEDAV_OK;
}
xml_node_t *xml_req = xml_parse(req->body, req->body_len);
if (!xml_req) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Invalid XML");
return NWEDAV_OK;
}
xml_node_t *lockinfo = xml_find(xml_req, DAV_NS, "lockinfo");
if (!lockinfo) {
xml_free(xml_req);
http_response_error(res, HTTP_400_BAD_REQUEST, "Missing lockinfo element");
return NWEDAV_OK;
}
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, req->uri, sizeof(lock.resource_path) - 1);
lock.user_id = ctx->user_id;
lock.type = LOCK_TYPE_WRITE;
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.depth = (req->depth == LOCK_DEPTH_INFINITY) ? LOCK_DEPTH_INFINITY : LOCK_DEPTH_0;
lock.timeout = req->timeout > 0 ? req->timeout : 3600;
xml_node_t *lockscope = xml_find(lockinfo, DAV_NS, "lockscope");
if (lockscope) {
if (xml_find_child(lockscope, DAV_NS, "shared")) {
lock.scope = LOCK_SCOPE_SHARED;
}
}
xml_node_t *owner = xml_find(lockinfo, DAV_NS, "owner");
if (owner) {
const char *owner_text = xml_get_content(owner);
if (owner_text) {
strncpy(lock.owner, owner_text, sizeof(lock.owner) - 1);
} else {
xml_node_t *href = xml_find_child(owner, DAV_NS, "href");
if (href) {
const char *href_text = xml_get_content(href);
if (href_text) {
strncpy(lock.owner, href_text, sizeof(lock.owner) - 1);
}
}
}
}
xml_free(xml_req);
int created = !storage_exists(ctx->storage, req->uri);
if (created) {
int ret = storage_write_file(ctx->storage, req->uri, "", 0);
if (ret != NWEDAV_OK && ret != NWEDAV_ERROR_CONFLICT) {
http_response_error(res, HTTP_409_CONFLICT, "Cannot create resource");
return NWEDAV_OK;
}
}
int ret = lock_manager_create_lock(ctx->locks, &lock);
if (ret == NWEDAV_ERROR_LOCKED) {
http_response_error(res, HTTP_423_LOCKED, "Resource already locked");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Failed to create lock");
return NWEDAV_OK;
}
buffer_t *buf = buffer_create(1024);
build_lock_response(&lock, buf);
http_response_set_status(res, created ? HTTP_201_CREATED : HTTP_200_OK);
http_response_add_header(res, "Content-Type", "application/xml; charset=utf-8");
char token_header[128];
snprintf(token_header, sizeof(token_header), "<%s>", lock.token);
http_response_add_header(res, "Lock-Token", token_header);
http_response_set_body(res, buf->data, buf->size);
buffer_destroy(buf);
return NWEDAV_OK;
}
int webdav_unlock(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!req->lock_token) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Lock-Token header required");
return NWEDAV_OK;
}
const char *token = req->lock_token;
if (*token == '<') token++;
char clean_token[64];
strncpy(clean_token, token, sizeof(clean_token) - 1);
clean_token[sizeof(clean_token) - 1] = '\0';
char *end = strchr(clean_token, '>');
if (end) *end = '\0';
lock_entry_t lock;
int ret = lock_manager_get_lock(ctx->locks, clean_token, &lock);
if (ret == NWEDAV_ERROR_NOT_FOUND) {
http_response_error(res, HTTP_409_CONFLICT, "Lock token not found");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Failed to get lock");
return NWEDAV_OK;
}
if (lock.user_id != ctx->user_id && ctx->user_id != 0) {
http_response_error(res, HTTP_403_FORBIDDEN, "Not lock owner");
return NWEDAV_OK;
}
ret = lock_manager_remove_lock(ctx->locks, clean_token);
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Failed to remove lock");
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_204_NO_CONTENT);
http_response_add_header(res, "Content-Length", "0");
return NWEDAV_OK;
}
+378
View File
@@ -0,0 +1,378 @@
#include "methods.h"
#include "xml.h"
#include "../platform/memory.h"
#include "../platform/time.h"
#include "../http/headers.h"
#include <string.h>
int webdav_dispatch(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
switch (req->method) {
case HTTP_METHOD_OPTIONS:
return webdav_options(ctx, req, res);
case HTTP_METHOD_GET:
return webdav_get(ctx, req, res);
case HTTP_METHOD_HEAD:
return webdav_head(ctx, req, res);
case HTTP_METHOD_PUT:
return webdav_put(ctx, req, res);
case HTTP_METHOD_DELETE:
return webdav_delete(ctx, req, res);
case HTTP_METHOD_MKCOL:
return webdav_mkcol(ctx, req, res);
case HTTP_METHOD_COPY:
return webdav_copy(ctx, req, res);
case HTTP_METHOD_MOVE:
return webdav_move(ctx, req, res);
case HTTP_METHOD_PROPFIND:
return webdav_propfind(ctx, req, res);
case HTTP_METHOD_PROPPATCH:
return webdav_proppatch(ctx, req, res);
case HTTP_METHOD_LOCK:
return webdav_lock(ctx, req, res);
case HTTP_METHOD_UNLOCK:
return webdav_unlock(ctx, req, res);
default:
http_response_error(res, HTTP_405_METHOD_NOT_ALLOWED, "Method not allowed");
http_response_add_header(res, "Allow", "OPTIONS, GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK");
return NWEDAV_OK;
}
}
int webdav_options(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
(void)ctx;
(void)req;
http_response_set_status(res, HTTP_200_OK);
http_response_add_header(res, "Allow", "OPTIONS, GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK");
http_response_add_header(res, "DAV", "1, 2");
http_response_add_header(res, "MS-Author-Via", "DAV");
http_response_add_header(res, "Content-Length", "0");
return NWEDAV_OK;
}
int webdav_get(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
resource_info_t info;
int ret = storage_get_info(ctx->storage, req->uri, &info);
if (ret == NWEDAV_ERROR_NOT_FOUND) {
http_response_error(res, HTTP_404_NOT_FOUND, "Resource not found");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Internal error");
return NWEDAV_OK;
}
if (info.is_collection) {
http_response_error(res, HTTP_403_FORBIDDEN, "Cannot GET a collection");
return NWEDAV_OK;
}
const char *if_none_match = http_get_header(req, "If-None-Match");
if (if_none_match && !http_check_if_none_match(if_none_match, info.etag)) {
http_response_set_status(res, HTTP_304_NOT_MODIFIED);
http_response_add_header(res, "ETag", info.etag);
return NWEDAV_OK;
}
const char *if_modified = http_get_header(req, "If-Modified-Since");
if (if_modified && !http_check_if_modified_since(if_modified, info.mtime)) {
http_response_set_status(res, HTTP_304_NOT_MODIFIED);
http_response_add_header(res, "ETag", info.etag);
return NWEDAV_OK;
}
char *data;
size_t len;
ret = storage_read_file(ctx->storage, req->uri, &data, &len);
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Cannot read file");
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_200_OK);
http_response_add_header(res, "Content-Type", info.content_type);
http_response_add_header(res, "ETag", info.etag);
char mtime_str[64];
time_format_http(info.mtime, mtime_str, sizeof(mtime_str));
http_response_add_header(res, "Last-Modified", mtime_str);
char len_str[32];
snprintf(len_str, sizeof(len_str), "%zu", len);
http_response_add_header(res, "Content-Length", len_str);
http_response_set_body(res, data, len);
free(data);
return NWEDAV_OK;
}
int webdav_head(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
resource_info_t info;
int ret = storage_get_info(ctx->storage, req->uri, &info);
if (ret == NWEDAV_ERROR_NOT_FOUND) {
http_response_error(res, HTTP_404_NOT_FOUND, "Resource not found");
res->body_len = 0;
free(res->body);
res->body = NULL;
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Internal error");
res->body_len = 0;
free(res->body);
res->body = NULL;
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_200_OK);
http_response_add_header(res, "Content-Type", info.content_type);
http_response_add_header(res, "ETag", info.etag);
char mtime_str[64];
time_format_http(info.mtime, mtime_str, sizeof(mtime_str));
http_response_add_header(res, "Last-Modified", mtime_str);
if (!info.is_collection) {
char len_str[32];
snprintf(len_str, sizeof(len_str), "%lld", (long long)info.size);
http_response_add_header(res, "Content-Length", len_str);
}
return NWEDAV_OK;
}
int webdav_put(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (ctx->quota_bytes > 0 && ctx->used_bytes + req->content_length > ctx->quota_bytes) {
http_response_error(res, HTTP_507_INSUFFICIENT_STORAGE, "Quota exceeded");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, req->uri, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
http_response_error(res, HTTP_423_LOCKED, "Resource is locked");
return NWEDAV_OK;
}
int existed = storage_exists(ctx->storage, req->uri);
ret = storage_write_file(ctx->storage, req->uri, req->body, req->body_len);
if (ret == NWEDAV_ERROR_CONFLICT) {
http_response_error(res, HTTP_409_CONFLICT, "Parent collection does not exist");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Cannot write file");
return NWEDAV_OK;
}
http_response_set_status(res, existed ? HTTP_204_NO_CONTENT : HTTP_201_CREATED);
http_response_add_header(res, "Content-Length", "0");
return NWEDAV_OK;
}
int webdav_delete(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!storage_exists(ctx->storage, req->uri)) {
http_response_error(res, HTTP_404_NOT_FOUND, "Resource not found");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, req->uri, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
http_response_error(res, HTTP_423_LOCKED, "Resource is locked");
return NWEDAV_OK;
}
ret = storage_delete(ctx->storage, req->uri);
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Cannot delete resource");
return NWEDAV_OK;
}
property_store_remove_all(ctx->properties, req->uri);
http_response_set_status(res, HTTP_204_NO_CONTENT);
http_response_add_header(res, "Content-Length", "0");
return NWEDAV_OK;
}
int webdav_mkcol(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (req->body && req->body_len > 0) {
http_response_error(res, HTTP_415_UNSUPPORTED_MEDIA_TYPE, "Request body not supported for MKCOL");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, req->uri, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
http_response_error(res, HTTP_423_LOCKED, "Resource is locked");
return NWEDAV_OK;
}
ret = storage_create_collection(ctx->storage, req->uri);
if (ret == NWEDAV_ERROR_EXISTS) {
http_response_error(res, HTTP_405_METHOD_NOT_ALLOWED, "Resource already exists");
return NWEDAV_OK;
}
if (ret == NWEDAV_ERROR_CONFLICT) {
http_response_error(res, HTTP_409_CONFLICT, "Parent collection does not exist");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create collection");
return NWEDAV_OK;
}
http_response_set_status(res, HTTP_201_CREATED);
http_response_add_header(res, "Content-Length", "0");
return NWEDAV_OK;
}
static char *extract_path_from_destination(const char *dest, const char *host) {
if (!dest) return NULL;
if (strncmp(dest, "http://", 7) == 0 || strncmp(dest, "https://", 8) == 0) {
const char *path_start = dest + (dest[4] == 's' ? 8 : 7);
path_start = strchr(path_start, '/');
if (path_start) {
return str_dup(path_start);
}
return str_dup("/");
}
(void)host;
return str_dup(dest);
}
int webdav_copy(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!req->destination) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Destination header required");
return NWEDAV_OK;
}
char *dest_path = extract_path_from_destination(req->destination, req->host);
if (!dest_path) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Invalid destination");
return NWEDAV_OK;
}
if (!storage_exists(ctx->storage, req->uri)) {
free(dest_path);
http_response_error(res, HTTP_404_NOT_FOUND, "Source not found");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, dest_path, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
free(dest_path);
http_response_error(res, HTTP_423_LOCKED, "Destination is locked");
return NWEDAV_OK;
}
int existed = storage_exists(ctx->storage, dest_path);
ret = storage_copy(ctx->storage, req->uri, dest_path, req->overwrite);
if (ret == NWEDAV_ERROR_EXISTS) {
free(dest_path);
http_response_error(res, HTTP_412_PRECONDITION_FAILED, "Destination exists and Overwrite is F");
return NWEDAV_OK;
}
if (ret == NWEDAV_ERROR_CONFLICT) {
free(dest_path);
http_response_error(res, HTTP_409_CONFLICT, "Parent collection does not exist");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
free(dest_path);
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Copy failed");
return NWEDAV_OK;
}
property_store_copy(ctx->properties, req->uri, dest_path);
http_response_set_status(res, existed ? HTTP_204_NO_CONTENT : HTTP_201_CREATED);
http_response_add_header(res, "Content-Length", "0");
free(dest_path);
return NWEDAV_OK;
}
int webdav_move(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!req->destination) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Destination header required");
return NWEDAV_OK;
}
char *dest_path = extract_path_from_destination(req->destination, req->host);
if (!dest_path) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Invalid destination");
return NWEDAV_OK;
}
if (!storage_exists(ctx->storage, req->uri)) {
free(dest_path);
http_response_error(res, HTTP_404_NOT_FOUND, "Source not found");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, req->uri, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
free(dest_path);
http_response_error(res, HTTP_423_LOCKED, "Source is locked");
return NWEDAV_OK;
}
ret = lock_manager_check_lock(ctx->locks, dest_path, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
free(dest_path);
http_response_error(res, HTTP_423_LOCKED, "Destination is locked");
return NWEDAV_OK;
}
int existed = storage_exists(ctx->storage, dest_path);
ret = storage_move(ctx->storage, req->uri, dest_path, req->overwrite);
if (ret == NWEDAV_ERROR_EXISTS) {
free(dest_path);
http_response_error(res, HTTP_412_PRECONDITION_FAILED, "Destination exists and Overwrite is F");
return NWEDAV_OK;
}
if (ret == NWEDAV_ERROR_CONFLICT) {
free(dest_path);
http_response_error(res, HTTP_409_CONFLICT, "Parent collection does not exist");
return NWEDAV_OK;
}
if (ret != NWEDAV_OK) {
free(dest_path);
http_response_error(res, HTTP_500_INTERNAL_SERVER_ERROR, "Move failed");
return NWEDAV_OK;
}
property_store_move(ctx->properties, req->uri, dest_path);
http_response_set_status(res, existed ? HTTP_204_NO_CONTENT : HTTP_201_CREATED);
http_response_add_header(res, "Content-Length", "0");
free(dest_path);
return NWEDAV_OK;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef WEBDAV_METHODS_H
#define WEBDAV_METHODS_H
#include "nwedav.h"
#include "../http/parser.h"
#include "../http/response.h"
#include "../storage/backend.h"
#include "../storage/properties.h"
#include "../storage/locks.h"
typedef struct {
storage_backend_t *storage;
property_store_t *properties;
lock_manager_t *locks;
uint32_t user_id;
char username[64];
uint64_t quota_bytes;
uint64_t used_bytes;
} webdav_context_t;
typedef int (*webdav_method_handler_t)(webdav_context_t *ctx,
http_request_t *req,
http_response_t *res);
int webdav_dispatch(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_options(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_get(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_head(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_put(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_delete(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_mkcol(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_copy(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_move(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_propfind(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_proppatch(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_lock(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
int webdav_unlock(webdav_context_t *ctx, http_request_t *req, http_response_t *res);
#endif
+268
View File
@@ -0,0 +1,268 @@
#include "methods.h"
#include "xml.h"
#include "../platform/memory.h"
#include "../platform/time.h"
#include <string.h>
typedef struct {
buffer_t *buf;
webdav_context_t *ctx;
xml_node_t *prop_request;
int allprop;
int propname;
lock_manager_t *locks;
} propfind_ctx_t;
static void add_live_properties(xml_node_t *prop, resource_info_t *info, lock_manager_t *locks) {
xml_node_t *node;
char ctime_str[64];
time_format_iso8601(info->ctime, ctime_str, sizeof(ctime_str));
node = xml_create_dav_element("creationdate");
xml_node_set_content(node, ctime_str);
xml_node_add_child(prop, node);
node = xml_create_dav_element("displayname");
xml_node_set_content(node, info->name);
xml_node_add_child(prop, node);
if (!info->is_collection) {
char size_str[32];
snprintf(size_str, sizeof(size_str), "%lld", (long long)info->size);
node = xml_create_dav_element("getcontentlength");
xml_node_set_content(node, size_str);
xml_node_add_child(prop, node);
node = xml_create_dav_element("getcontenttype");
xml_node_set_content(node, info->content_type);
xml_node_add_child(prop, node);
}
node = xml_create_dav_element("getetag");
xml_node_set_content(node, info->etag);
xml_node_add_child(prop, node);
char mtime_str[64];
time_format_http(info->mtime, mtime_str, sizeof(mtime_str));
node = xml_create_dav_element("getlastmodified");
xml_node_set_content(node, mtime_str);
xml_node_add_child(prop, node);
node = xml_create_dav_element("resourcetype");
if (info->is_collection) {
xml_node_t *col = xml_create_dav_element("collection");
xml_node_add_child(node, col);
}
xml_node_add_child(prop, node);
node = xml_create_dav_element("supportedlock");
xml_node_t *lockentry = xml_create_dav_element("lockentry");
xml_node_t *lockscope = xml_create_dav_element("lockscope");
xml_node_t *exclusive = xml_create_dav_element("exclusive");
xml_node_add_child(lockscope, exclusive);
xml_node_add_child(lockentry, lockscope);
xml_node_t *locktype = xml_create_dav_element("locktype");
xml_node_t *write = xml_create_dav_element("write");
xml_node_add_child(locktype, write);
xml_node_add_child(lockentry, locktype);
xml_node_add_child(node, lockentry);
xml_node_add_child(prop, node);
if (locks) {
lock_entry_t *lock_list;
int lock_count;
if (lock_manager_get_locks_for_path(locks, info->path, &lock_list, &lock_count) == NWEDAV_OK && lock_count > 0) {
node = xml_create_dav_element("lockdiscovery");
for (int i = 0; i < lock_count; i++) {
xml_node_t *activelock = xml_create_dav_element("activelock");
xml_node_t *lt = xml_create_dav_element("locktype");
xml_node_t *w = xml_create_dav_element("write");
xml_node_add_child(lt, w);
xml_node_add_child(activelock, lt);
xml_node_t *ls = xml_create_dav_element("lockscope");
xml_node_t *scope = xml_create_dav_element(lock_list[i].scope == LOCK_SCOPE_EXCLUSIVE ? "exclusive" : "shared");
xml_node_add_child(ls, scope);
xml_node_add_child(activelock, ls);
xml_node_t *depth_node = xml_create_dav_element("depth");
xml_node_set_content(depth_node, lock_list[i].depth == LOCK_DEPTH_INFINITY ? "infinity" : "0");
xml_node_add_child(activelock, depth_node);
if (lock_list[i].owner[0]) {
xml_node_t *owner = xml_create_dav_element("owner");
xml_node_set_content(owner, lock_list[i].owner);
xml_node_add_child(activelock, owner);
}
xml_node_t *timeout_node = xml_create_dav_element("timeout");
int64_t remaining = lock_list[i].timeout - time(NULL);
char timeout_str[64];
if (remaining > 0) {
snprintf(timeout_str, sizeof(timeout_str), "Second-%lld", (long long)remaining);
} else {
strcpy(timeout_str, "Second-0");
}
xml_node_set_content(timeout_node, timeout_str);
xml_node_add_child(activelock, timeout_node);
xml_node_t *locktoken = xml_create_dav_element("locktoken");
xml_node_t *href = xml_create_dav_element("href");
xml_node_set_content(href, lock_list[i].token);
xml_node_add_child(locktoken, href);
xml_node_add_child(activelock, locktoken);
xml_node_add_child(node, activelock);
}
xml_node_add_child(prop, node);
free(lock_list);
}
}
}
static void add_dead_properties(xml_node_t *prop, webdav_context_t *ctx, const char *path) {
(void)prop;
(void)ctx;
(void)path;
}
static int propfind_callback(const char *path, resource_info_t *info, void *user_data) {
propfind_ctx_t *pctx = (propfind_ctx_t *)user_data;
xml_node_t *response = xml_create_dav_element("response");
xml_node_t *href = xml_create_dav_element("href");
xml_node_set_content(href, path);
xml_node_add_child(response, href);
xml_node_t *propstat = xml_create_dav_element("propstat");
xml_node_t *prop = xml_create_dav_element("prop");
if (pctx->propname) {
xml_node_add_child(prop, xml_create_dav_element("creationdate"));
xml_node_add_child(prop, xml_create_dav_element("displayname"));
xml_node_add_child(prop, xml_create_dav_element("getcontentlength"));
xml_node_add_child(prop, xml_create_dav_element("getcontenttype"));
xml_node_add_child(prop, xml_create_dav_element("getetag"));
xml_node_add_child(prop, xml_create_dav_element("getlastmodified"));
xml_node_add_child(prop, xml_create_dav_element("resourcetype"));
xml_node_add_child(prop, xml_create_dav_element("supportedlock"));
xml_node_add_child(prop, xml_create_dav_element("lockdiscovery"));
} else if (pctx->allprop || !pctx->prop_request) {
add_live_properties(prop, info, pctx->locks);
add_dead_properties(prop, pctx->ctx, path);
} else {
xml_node_t *req_prop = xml_find_child(pctx->prop_request, DAV_NS, "prop");
if (req_prop) {
xml_node_t *child = req_prop->children;
while (child) {
if (child->type == XML_NODE_ELEMENT && child->name) {
int found = 0;
if (strcmp(child->name, "creationdate") == 0 ||
strcmp(child->name, "displayname") == 0 ||
strcmp(child->name, "getcontentlength") == 0 ||
strcmp(child->name, "getcontenttype") == 0 ||
strcmp(child->name, "getetag") == 0 ||
strcmp(child->name, "getlastmodified") == 0 ||
strcmp(child->name, "resourcetype") == 0 ||
strcmp(child->name, "supportedlock") == 0 ||
strcmp(child->name, "lockdiscovery") == 0) {
add_live_properties(prop, info, pctx->locks);
found = 1;
}
(void)found;
}
child = child->next;
}
} else {
add_live_properties(prop, info, pctx->locks);
}
}
xml_node_add_child(propstat, prop);
xml_node_t *status = xml_create_dav_element("status");
xml_node_set_content(status, "HTTP/1.1 200 OK");
xml_node_add_child(propstat, status);
xml_node_add_child(response, propstat);
size_t len;
char *xml = xml_serialize(response, &len);
xml_free(response);
if (xml) {
const char *start = strstr(xml, "<D:response");
if (start) {
buffer_append_str(pctx->buf, start);
}
free(xml);
}
return 0;
}
int webdav_propfind(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!storage_exists(ctx->storage, req->uri)) {
http_response_error(res, HTTP_404_NOT_FOUND, "Resource not found");
return NWEDAV_OK;
}
int depth = req->depth;
if (depth < 0) depth = LOCK_DEPTH_INFINITY;
if (depth > LOCK_DEPTH_INFINITY) depth = LOCK_DEPTH_INFINITY;
xml_node_t *xml_req = NULL;
int allprop = 1;
int propname = 0;
if (req->body && req->body_len > 0) {
xml_req = xml_parse(req->body, req->body_len);
if (xml_req) {
xml_node_t *propfind = xml_find(xml_req, DAV_NS, "propfind");
if (propfind) {
if (xml_find_child(propfind, DAV_NS, "propname")) {
propname = 1;
allprop = 0;
} else if (xml_find_child(propfind, DAV_NS, "allprop")) {
allprop = 1;
} else if (xml_find_child(propfind, DAV_NS, "prop")) {
allprop = 0;
}
}
}
}
buffer_t *buf = buffer_create(8192);
buffer_append_str(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
buffer_append_str(buf, "<D:multistatus xmlns:D=\"DAV:\">");
propfind_ctx_t pctx = {
.buf = buf,
.ctx = ctx,
.prop_request = xml_req,
.allprop = allprop,
.propname = propname,
.locks = ctx->locks
};
int list_depth = (depth == LOCK_DEPTH_0) ? 0 : (depth == LOCK_DEPTH_1) ? 1 : 100;
storage_list(ctx->storage, req->uri, list_depth, propfind_callback, &pctx);
buffer_append_str(buf, "</D:multistatus>");
if (xml_req) {
xml_free(xml_req);
}
http_response_set_status(res, HTTP_207_MULTI_STATUS);
http_response_add_header(res, "Content-Type", "application/xml; charset=utf-8");
http_response_set_body(res, buf->data, buf->size);
buffer_destroy(buf);
return NWEDAV_OK;
}
+106
View File
@@ -0,0 +1,106 @@
#include "methods.h"
#include "xml.h"
#include "../platform/memory.h"
#include <string.h>
int webdav_proppatch(webdav_context_t *ctx, http_request_t *req, http_response_t *res) {
if (!storage_exists(ctx->storage, req->uri)) {
http_response_error(res, HTTP_404_NOT_FOUND, "Resource not found");
return NWEDAV_OK;
}
int ret = lock_manager_check_lock(ctx->locks, req->uri, req->lock_token, ctx->user_id);
if (ret == NWEDAV_ERROR_LOCKED) {
http_response_error(res, HTTP_423_LOCKED, "Resource is locked");
return NWEDAV_OK;
}
if (!req->body || req->body_len == 0) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Request body required");
return NWEDAV_OK;
}
xml_node_t *xml_req = xml_parse(req->body, req->body_len);
if (!xml_req) {
http_response_error(res, HTTP_400_BAD_REQUEST, "Invalid XML");
return NWEDAV_OK;
}
xml_node_t *propertyupdate = xml_find(xml_req, DAV_NS, "propertyupdate");
if (!propertyupdate) {
xml_free(xml_req);
http_response_error(res, HTTP_400_BAD_REQUEST, "Missing propertyupdate element");
return NWEDAV_OK;
}
buffer_t *buf = buffer_create(4096);
buffer_append_str(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
buffer_append_str(buf, "<D:multistatus xmlns:D=\"DAV:\">");
buffer_append_str(buf, "<D:response>");
buffer_append_fmt(buf, "<D:href>%s</D:href>", req->uri);
xml_node_t *child = propertyupdate->children;
while (child) {
if (child->type == XML_NODE_ELEMENT) {
int is_set = (child->name && strcmp(child->name, "set") == 0);
int is_remove = (child->name && strcmp(child->name, "remove") == 0);
if (is_set || is_remove) {
xml_node_t *prop = xml_find_child(child, DAV_NS, "prop");
if (prop) {
xml_node_t *prop_child = prop->children;
while (prop_child) {
if (prop_child->type == XML_NODE_ELEMENT && prop_child->name) {
const char *ns = prop_child->namespace ? prop_child->namespace : "";
const char *name = prop_child->name;
int prop_ret;
if (is_set) {
const char *value = xml_get_content(prop_child);
if (!value) {
size_t len;
char *serialized = xml_serialize(prop_child, &len);
prop_ret = property_store_set(ctx->properties, req->uri, ns, name, serialized);
free(serialized);
} else {
prop_ret = property_store_set(ctx->properties, req->uri, ns, name, value);
}
} else {
prop_ret = property_store_remove(ctx->properties, req->uri, ns, name);
}
buffer_append_str(buf, "<D:propstat><D:prop>");
if (prop_child->prefix) {
buffer_append_fmt(buf, "<%s:%s xmlns:%s=\"%s\"/>",
prop_child->prefix, name, prop_child->prefix, ns);
} else if (ns[0]) {
buffer_append_fmt(buf, "<%s xmlns=\"%s\"/>", name, ns);
} else {
buffer_append_fmt(buf, "<%s/>", name);
}
buffer_append_str(buf, "</D:prop>");
buffer_append_fmt(buf, "<D:status>HTTP/1.1 %s</D:status>",
prop_ret == NWEDAV_OK ? "200 OK" : "403 Forbidden");
buffer_append_str(buf, "</D:propstat>");
}
prop_child = prop_child->next;
}
}
}
}
child = child->next;
}
buffer_append_str(buf, "</D:response>");
buffer_append_str(buf, "</D:multistatus>");
xml_free(xml_req);
http_response_set_status(res, HTTP_207_MULTI_STATUS);
http_response_add_header(res, "Content-Type", "application/xml; charset=utf-8");
http_response_set_body(res, buf->data, buf->size);
buffer_destroy(buf);
return NWEDAV_OK;
}
+615
View File
@@ -0,0 +1,615 @@
#include "xml.h"
#include <string.h>
#include <ctype.h>
typedef struct {
const char *data;
size_t len;
size_t pos;
char *default_ns;
struct {
char *prefix;
char *uri;
} namespaces[32];
int ns_count;
} xml_parser_t;
static void skip_whitespace(xml_parser_t *p) {
while (p->pos < p->len && isspace((unsigned char)p->data[p->pos])) {
p->pos++;
}
}
static int match(xml_parser_t *p, const char *str) {
size_t len = strlen(str);
if (p->pos + len > p->len) return 0;
if (strncmp(p->data + p->pos, str, len) == 0) {
p->pos += len;
return 1;
}
return 0;
}
static char *parse_name(xml_parser_t *p) {
skip_whitespace(p);
size_t start = p->pos;
if (p->pos < p->len && (isalpha((unsigned char)p->data[p->pos]) ||
p->data[p->pos] == '_' || p->data[p->pos] == ':')) {
p->pos++;
while (p->pos < p->len && (isalnum((unsigned char)p->data[p->pos]) ||
p->data[p->pos] == '_' ||
p->data[p->pos] == '-' ||
p->data[p->pos] == '.' ||
p->data[p->pos] == ':')) {
p->pos++;
}
}
if (p->pos == start) return NULL;
return str_ndup(p->data + start, p->pos - start);
}
static char *parse_string(xml_parser_t *p) {
skip_whitespace(p);
char quote = p->data[p->pos];
if (quote != '"' && quote != '\'') return NULL;
p->pos++;
size_t start = p->pos;
while (p->pos < p->len && p->data[p->pos] != quote) {
p->pos++;
}
char *str = str_ndup(p->data + start, p->pos - start);
if (p->pos < p->len) p->pos++;
return str;
}
static char *decode_entities(const char *str) {
if (!str) return NULL;
buffer_t *buf = buffer_create(strlen(str) + 1);
if (!buf) return str_dup(str);
const char *p = str;
while (*p) {
if (*p == '&') {
if (strncmp(p, "&lt;", 4) == 0) {
buffer_append(buf, "<", 1);
p += 4;
} else if (strncmp(p, "&gt;", 4) == 0) {
buffer_append(buf, ">", 1);
p += 4;
} else if (strncmp(p, "&amp;", 5) == 0) {
buffer_append(buf, "&", 1);
p += 5;
} else if (strncmp(p, "&apos;", 6) == 0) {
buffer_append(buf, "'", 1);
p += 6;
} else if (strncmp(p, "&quot;", 6) == 0) {
buffer_append(buf, "\"", 1);
p += 6;
} else {
buffer_append(buf, p, 1);
p++;
}
} else {
buffer_append(buf, p, 1);
p++;
}
}
char *result = buffer_detach(buf);
buffer_destroy(buf);
return result;
}
static const char *lookup_namespace(xml_parser_t *p, const char *prefix) {
for (int i = p->ns_count - 1; i >= 0; i--) {
if ((prefix == NULL && p->namespaces[i].prefix == NULL) ||
(prefix && p->namespaces[i].prefix && strcmp(prefix, p->namespaces[i].prefix) == 0)) {
return p->namespaces[i].uri;
}
}
if (prefix == NULL) return p->default_ns;
return NULL;
}
static void add_namespace(xml_parser_t *p, const char *prefix, const char *uri) {
if (p->ns_count >= 32) return;
p->namespaces[p->ns_count].prefix = prefix ? str_dup(prefix) : NULL;
p->namespaces[p->ns_count].uri = str_dup(uri);
p->ns_count++;
}
static xml_node_t *parse_element(xml_parser_t *p);
static char *parse_content(xml_parser_t *p) {
size_t start = p->pos;
while (p->pos < p->len && p->data[p->pos] != '<') {
p->pos++;
}
if (p->pos == start) return NULL;
char *raw = str_ndup(p->data + start, p->pos - start);
char *decoded = decode_entities(raw);
free(raw);
char *trimmed = str_trim(decoded);
if (trimmed != decoded && trimmed[0] != '\0') {
char *result = str_dup(trimmed);
free(decoded);
return result;
}
if (trimmed[0] == '\0') {
free(decoded);
return NULL;
}
return decoded;
}
static xml_node_t *parse_element(xml_parser_t *p) {
skip_whitespace(p);
if (p->pos >= p->len || p->data[p->pos] != '<') {
return NULL;
}
p->pos++;
if (p->data[p->pos] == '?') {
while (p->pos < p->len - 1) {
if (p->data[p->pos] == '?' && p->data[p->pos + 1] == '>') {
p->pos += 2;
return parse_element(p);
}
p->pos++;
}
return NULL;
}
if (p->data[p->pos] == '!') {
if (strncmp(p->data + p->pos, "!--", 3) == 0) {
p->pos += 3;
while (p->pos < p->len - 2) {
if (strncmp(p->data + p->pos, "-->", 3) == 0) {
p->pos += 3;
return parse_element(p);
}
p->pos++;
}
} else if (strncmp(p->data + p->pos, "![CDATA[", 8) == 0) {
p->pos += 8;
size_t start = p->pos;
while (p->pos < p->len - 2) {
if (strncmp(p->data + p->pos, "]]>", 3) == 0) {
xml_node_t *node = xml_node_create(XML_NODE_CDATA, NULL);
node->content = str_ndup(p->data + start, p->pos - start);
p->pos += 3;
return node;
}
p->pos++;
}
}
return NULL;
}
char *full_name = parse_name(p);
if (!full_name) return NULL;
xml_node_t *node = xml_node_create(XML_NODE_ELEMENT, NULL);
char *colon = strchr(full_name, ':');
if (colon) {
*colon = '\0';
node->prefix = str_dup(full_name);
node->name = str_dup(colon + 1);
*colon = ':';
} else {
node->name = str_dup(full_name);
}
free(full_name);
int saved_ns_count = p->ns_count;
while (1) {
skip_whitespace(p);
if (p->pos >= p->len) break;
if (p->data[p->pos] == '/' && p->pos + 1 < p->len && p->data[p->pos + 1] == '>') {
p->pos += 2;
node->namespace = str_dup(lookup_namespace(p, node->prefix));
p->ns_count = saved_ns_count;
return node;
}
if (p->data[p->pos] == '>') {
p->pos++;
break;
}
char *attr_name = parse_name(p);
if (!attr_name) break;
skip_whitespace(p);
if (p->data[p->pos] != '=') {
free(attr_name);
continue;
}
p->pos++;
char *attr_value = parse_string(p);
if (strcmp(attr_name, "xmlns") == 0) {
free(p->default_ns);
p->default_ns = attr_value;
add_namespace(p, NULL, attr_value);
} else if (strncmp(attr_name, "xmlns:", 6) == 0) {
add_namespace(p, attr_name + 6, attr_value);
free(attr_value);
} else {
xml_node_add_attr(node, attr_name, attr_value);
free(attr_value);
}
free(attr_name);
}
node->namespace = str_dup(lookup_namespace(p, node->prefix));
while (p->pos < p->len) {
skip_whitespace(p);
if (p->pos >= p->len) break;
if (p->data[p->pos] == '<' && p->pos + 1 < p->len && p->data[p->pos + 1] == '/') {
p->pos += 2;
char *end_name = parse_name(p);
free(end_name);
skip_whitespace(p);
if (p->pos < p->len && p->data[p->pos] == '>') {
p->pos++;
}
break;
}
if (p->data[p->pos] == '<') {
xml_node_t *child = parse_element(p);
if (child) {
xml_node_add_child(node, child);
}
} else {
char *content = parse_content(p);
if (content && content[0]) {
if (node->content) {
size_t old_len = strlen(node->content);
size_t new_len = strlen(content);
char *combined = malloc(old_len + new_len + 1);
strcpy(combined, node->content);
strcat(combined, content);
free(node->content);
node->content = combined;
free(content);
} else {
node->content = content;
}
} else {
free(content);
}
}
}
p->ns_count = saved_ns_count;
return node;
}
xml_node_t *xml_parse(const char *data, size_t len) {
if (!data || len == 0) return NULL;
xml_parser_t parser = {0};
parser.data = data;
parser.len = len;
parser.pos = 0;
parser.default_ns = NULL;
xml_node_t *root = parse_element(&parser);
free(parser.default_ns);
for (int i = 0; i < parser.ns_count; i++) {
free(parser.namespaces[i].prefix);
free(parser.namespaces[i].uri);
}
return root;
}
void xml_free(xml_node_t *node) {
if (!node) return;
free(node->name);
free(node->namespace);
free(node->prefix);
free(node->content);
xml_attr_t *attr = node->attrs;
while (attr) {
xml_attr_t *next = attr->next;
free(attr->name);
free(attr->value);
free(attr);
attr = next;
}
xml_node_t *child = node->children;
while (child) {
xml_node_t *next = child->next;
xml_free(child);
child = next;
}
free(node);
}
xml_node_t *xml_node_create(xml_node_type_t type, const char *name) {
xml_node_t *node = calloc(1, sizeof(xml_node_t));
if (!node) return NULL;
node->type = type;
if (name) node->name = str_dup(name);
return node;
}
void xml_node_set_namespace(xml_node_t *node, const char *ns, const char *prefix) {
if (!node) return;
free(node->namespace);
free(node->prefix);
node->namespace = ns ? str_dup(ns) : NULL;
node->prefix = prefix ? str_dup(prefix) : NULL;
}
void xml_node_set_content(xml_node_t *node, const char *content) {
if (!node) return;
free(node->content);
node->content = content ? str_dup(content) : NULL;
}
void xml_node_add_child(xml_node_t *parent, xml_node_t *child) {
if (!parent || !child) return;
child->parent = parent;
child->next = NULL;
if (!parent->children) {
parent->children = child;
} else {
xml_node_t *last = parent->children;
while (last->next) last = last->next;
last->next = child;
}
}
void xml_node_add_attr(xml_node_t *node, const char *name, const char *value) {
if (!node || !name) return;
xml_attr_t *attr = calloc(1, sizeof(xml_attr_t));
if (!attr) return;
attr->name = str_dup(name);
attr->value = value ? str_dup(value) : NULL;
attr->next = node->attrs;
node->attrs = attr;
}
xml_node_t *xml_find(xml_node_t *root, const char *ns, const char *name) {
if (!root || !name) return NULL;
if (root->type == XML_NODE_ELEMENT && strcmp(root->name, name) == 0) {
if (!ns || (root->namespace && strcmp(root->namespace, ns) == 0)) {
return root;
}
}
xml_node_t *found = xml_find_child(root, ns, name);
if (found) return found;
xml_node_t *child = root->children;
while (child) {
found = xml_find(child, ns, name);
if (found) return found;
child = child->next;
}
return NULL;
}
xml_node_t *xml_find_child(xml_node_t *parent, const char *ns, const char *name) {
if (!parent || !name) return NULL;
xml_node_t *child = parent->children;
while (child) {
if (child->type == XML_NODE_ELEMENT && child->name && strcmp(child->name, name) == 0) {
if (!ns || (child->namespace && strcmp(child->namespace, ns) == 0)) {
return child;
}
}
child = child->next;
}
return NULL;
}
const char *xml_get_attr(xml_node_t *node, const char *name) {
if (!node || !name) return NULL;
xml_attr_t *attr = node->attrs;
while (attr) {
if (strcmp(attr->name, name) == 0) {
return attr->value;
}
attr = attr->next;
}
return NULL;
}
const char *xml_get_content(xml_node_t *node) {
if (!node) return NULL;
return node->content;
}
static void serialize_node(xml_node_t *node, buffer_t *buf, int depth);
static void encode_text(buffer_t *buf, const char *text) {
while (*text) {
switch (*text) {
case '<': buffer_append_str(buf, "&lt;"); break;
case '>': buffer_append_str(buf, "&gt;"); break;
case '&': buffer_append_str(buf, "&amp;"); break;
case '"': buffer_append_str(buf, "&quot;"); break;
default: buffer_append(buf, text, 1); break;
}
text++;
}
}
static void serialize_node(xml_node_t *node, buffer_t *buf, int depth) {
if (!node) return;
if (node->type == XML_NODE_TEXT) {
if (node->content) {
encode_text(buf, node->content);
}
return;
}
if (node->type == XML_NODE_CDATA) {
buffer_append_str(buf, "<![CDATA[");
if (node->content) buffer_append_str(buf, node->content);
buffer_append_str(buf, "]]>");
return;
}
buffer_append_str(buf, "<");
if (node->prefix) {
buffer_append_str(buf, node->prefix);
buffer_append_str(buf, ":");
}
buffer_append_str(buf, node->name);
if (node->namespace && depth == 0) {
if (node->prefix) {
buffer_append_fmt(buf, " xmlns:%s=\"%s\"", node->prefix, node->namespace);
} else {
buffer_append_fmt(buf, " xmlns=\"%s\"", node->namespace);
}
}
xml_attr_t *attr = node->attrs;
while (attr) {
buffer_append_fmt(buf, " %s=\"", attr->name);
if (attr->value) encode_text(buf, attr->value);
buffer_append_str(buf, "\"");
attr = attr->next;
}
if (!node->children && !node->content) {
buffer_append_str(buf, "/>");
return;
}
buffer_append_str(buf, ">");
if (node->content) {
encode_text(buf, node->content);
}
xml_node_t *child = node->children;
while (child) {
serialize_node(child, buf, depth + 1);
child = child->next;
}
buffer_append_str(buf, "</");
if (node->prefix) {
buffer_append_str(buf, node->prefix);
buffer_append_str(buf, ":");
}
buffer_append_str(buf, node->name);
buffer_append_str(buf, ">");
}
char *xml_serialize(xml_node_t *root, size_t *out_len) {
if (!root) return NULL;
buffer_t *buf = buffer_create(4096);
if (!buf) return NULL;
buffer_append_str(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
serialize_node(root, buf, 0);
if (out_len) *out_len = buf->size;
char *result = buffer_detach(buf);
buffer_destroy(buf);
return result;
}
int xml_serialize_to_buffer(xml_node_t *root, buffer_t *buf) {
if (!root || !buf) return NWEDAV_ERROR;
buffer_append_str(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
serialize_node(root, buf, 0);
return NWEDAV_OK;
}
xml_node_t *xml_create_dav_element(const char *name) {
xml_node_t *node = xml_node_create(XML_NODE_ELEMENT, name);
if (node) {
xml_node_set_namespace(node, DAV_NS, "D");
}
return node;
}
xml_node_t *xml_create_multistatus(void) {
return xml_create_dav_element("multistatus");
}
xml_node_t *xml_create_response(const char *href) {
xml_node_t *response = xml_create_dav_element("response");
if (response && href) {
xml_node_t *href_node = xml_create_dav_element("href");
xml_node_set_content(href_node, href);
xml_node_add_child(response, href_node);
}
return response;
}
xml_node_t *xml_create_propstat(int status) {
xml_node_t *propstat = xml_create_dav_element("propstat");
if (propstat) {
xml_node_t *status_node = xml_create_dav_element("status");
char status_text[64];
snprintf(status_text, sizeof(status_text), "HTTP/1.1 %d %s",
status, status == 200 ? "OK" : "Not Found");
xml_node_set_content(status_node, status_text);
xml_node_add_child(propstat, status_node);
}
return propstat;
}
xml_node_t *xml_create_prop(void) {
return xml_create_dav_element("prop");
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef WEBDAV_XML_H
#define WEBDAV_XML_H
#include "nwedav.h"
#include "../platform/memory.h"
#define DAV_NS "DAV:"
typedef enum {
XML_NODE_ELEMENT,
XML_NODE_TEXT,
XML_NODE_CDATA
} xml_node_type_t;
typedef struct xml_attr {
char *name;
char *value;
struct xml_attr *next;
} xml_attr_t;
typedef struct xml_node {
xml_node_type_t type;
char *name;
char *namespace;
char *prefix;
char *content;
xml_attr_t *attrs;
struct xml_node *children;
struct xml_node *next;
struct xml_node *parent;
} xml_node_t;
xml_node_t *xml_parse(const char *data, size_t len);
void xml_free(xml_node_t *root);
xml_node_t *xml_node_create(xml_node_type_t type, const char *name);
void xml_node_set_namespace(xml_node_t *node, const char *ns, const char *prefix);
void xml_node_set_content(xml_node_t *node, const char *content);
void xml_node_add_child(xml_node_t *parent, xml_node_t *child);
void xml_node_add_attr(xml_node_t *node, const char *name, const char *value);
xml_node_t *xml_find(xml_node_t *root, const char *ns, const char *name);
xml_node_t *xml_find_child(xml_node_t *parent, const char *ns, const char *name);
const char *xml_get_attr(xml_node_t *node, const char *name);
const char *xml_get_content(xml_node_t *node);
char *xml_serialize(xml_node_t *root, size_t *out_len);
int xml_serialize_to_buffer(xml_node_t *root, buffer_t *buf);
xml_node_t *xml_create_dav_element(const char *name);
xml_node_t *xml_create_multistatus(void);
xml_node_t *xml_create_response(const char *href);
xml_node_t *xml_create_propstat(int status);
xml_node_t *xml_create_prop(void);
#endif