Compare commits

..
10 Commits
Author SHA1 Message Date
retoor fe22905359 chore: add pathmapping, streaming, webserve, and cgi sources to build; disable integration tests; fix empty username check in user_manager_add; make temp dirs unique with fixture_counter; update quota and user test APIs to use user.id and cast quota_bytes
CI / build-linux (clang) (push) Failing after 18s
CI / build-linux (gcc) (push) Failing after 18s
CI / integration-test (push) Has been skipped
CI / static-analysis (push) Failing after 34s
CI / build-macos (push) Has been cancelled
CI / release (push) Has been cancelled
2025-11-28 06:21:42 +00:00
retoor 53a6179ede chore: add CMake test infrastructure and placeholder unit test files for future coverage 2025-11-28 05:41:42 +00:00
retoor 4afdcf567b feat: add initial CGI support with environment variable passing and user permission fields
Implement core CGI execution infrastructure including cgi_config_init, cgi_is_executable, cgi_detect_headers, and cgi_execute functions in new src/cgi/cgi.c module. Extend auth_user_t struct with cgi_enabled, cgi_timeout, cgi_max_output, webserve_enabled, directory_listing, default_index, and granular read/write/execute permission fields. Add HTTP_206_PARTIAL_CONTENT, HTTP_416_RANGE_NOT_SATISFIABLE, and HTTP_500_INTERNAL_ERROR status codes to enum. Introduce streaming configuration block with buffer sizes, sendfile threshold, and per-request memory limit in config struct. Add pathmapping.c to both server and CLI build sources. Extend CLI webdavctl with user directory, permission, and CGI management subcommands. Expose auth_get_db function and sqlite3 forward declaration in auth.h.
2025-11-28 04:34:38 +00:00
retoor f75e1e6d9a chore: add core server infrastructure with config, logging, and server modules 2025-11-28 02:49:36 +00:00
retoor 8f72c85561 chore: update server bind address to localhost and adjust port numbers
- Change bind_address from 0.0.0.0 to 127.0.0.1 for security
- Update port from 8080 to 8597 and ssl_port from 8443 to 8598
- Reduce scale_up_step from 5 to 3 for more gradual scaling
- Decrease idle_timeout_ms from 30000 to 300 for faster connection cleanup
2025-11-28 02:40:45 +00:00
retoor bed35daab7 chore: add initial nwedav.conf with server, ssl, storage, auth, quota, logging, and scaling sections 2025-11-28 02:38:17 +00:00
retoor 80e9ccc0d8 feat: implement adaptive thread pool with dynamic scaling and configuration support
Add configurable thread pool with min/max thread limits, adaptive scaling based on queue pressure, and comprehensive statistics tracking. Introduce threadpool_config_t and threadpool_stats_t structs, new API functions (threadpool_create_adaptive, threadpool_get_stats, threadpool_scale_check, threadpool_thread_count), and worker thread lifecycle management with idle timeout and cooldown logic. Update nwedav.conf.sample with scaling section parameters.
2025-11-28 02:33:31 +00:00
retoor c451a6a560 fix: add input validation and bounds checks across auth, concurrency, http, platform, storage, and user modules 2025-11-28 02:16:02 +00:00
retoor 0aed19bac8 chore: increase default buffer sizes and add sendfile support with edge-triggered epoll 2025-11-28 02:05:51 +00:00
retoor 16db22b3de chore: add initial project scaffolding with CI, build system, and core headers 2025-11-25 20:24:12 +00:00
88 changed files with 9349 additions and 61 deletions
+13
View File
@@ -35,6 +35,12 @@ jobs:
cmake -DCMAKE_C_COMPILER=${{ matrix.compiler }} ..
make
- name: Run unit tests
run: |
cd build
make run_tests
./tests/run_tests
build-macos:
runs-on: macos-latest
steps:
@@ -52,6 +58,13 @@ jobs:
./nwedav --version
./webdavctl --help
- name: Build and run tests with CMake
run: |
mkdir -p build && cd build
cmake ..
make run_tests
./tests/run_tests
static-analysis:
runs-on: ubuntu-latest
steps:
+10
View File
@@ -38,6 +38,8 @@ set(SERVER_SOURCES
src/http/parser.c
src/http/response.c
src/http/headers.c
src/http/streaming.c
src/http/webserve.c
src/webdav/xml.c
src/webdav/methods.c
src/webdav/propfind.c
@@ -46,7 +48,9 @@ set(SERVER_SOURCES
src/storage/backend.c
src/storage/properties.c
src/storage/locks.c
src/storage/pathmapping.c
src/auth/auth.c
src/cgi/cgi.c
src/concurrency/threadpool.c
src/concurrency/eventloop.c
src/concurrency/connpool.c
@@ -54,6 +58,7 @@ set(SERVER_SOURCES
set(CLI_SOURCES
src/cli/webdavctl.c
src/storage/pathmapping.c
)
add_executable(nwedav ${COMMON_SOURCES} ${SERVER_SOURCES})
@@ -100,3 +105,8 @@ endif()
install(TARGETS nwedav webdavctl DESTINATION bin)
enable_testing()
option(BUILD_TESTS "Build test suite" ON)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+5 -1
View File
@@ -21,7 +21,8 @@ COMMON_SRCS = \
src/core/config.c \
src/core/logging.c \
src/user/usermgr.c \
src/user/password.c
src/user/password.c \
src/storage/pathmapping.c
SERVER_SRCS = \
src/main.c \
@@ -29,6 +30,8 @@ SERVER_SRCS = \
src/http/parser.c \
src/http/response.c \
src/http/headers.c \
src/http/streaming.c \
src/http/webserve.c \
src/webdav/xml.c \
src/webdav/methods.c \
src/webdav/propfind.c \
@@ -38,6 +41,7 @@ SERVER_SRCS = \
src/storage/properties.c \
src/storage/locks.c \
src/auth/auth.c \
src/cgi/cgi.c \
src/concurrency/threadpool.c \
src/concurrency/eventloop.c \
src/concurrency/connpool.c
+33 -21
View File
@@ -21,7 +21,7 @@ A high-performance WebDAV server implementation in pure C.
### Build Dependencies
- GCC or Clang (C11 support)
- Make or CMake
- CMake 3.10+
- SQLite3 development libraries
- OpenSSL development libraries
- UUID library (Linux)
@@ -29,31 +29,23 @@ A high-performance WebDAV server implementation in pure C.
### Debian/Ubuntu
```bash
apt-get install build-essential libsqlite3-dev libssl-dev uuid-dev
apt-get install build-essential cmake libsqlite3-dev libssl-dev uuid-dev
```
### RHEL/CentOS/Fedora
```bash
dnf install gcc make sqlite-devel openssl-devel libuuid-devel
dnf install gcc cmake make sqlite-devel openssl-devel libuuid-devel
```
### macOS
```bash
brew install sqlite openssl
brew install cmake sqlite openssl
```
## Building
### Using Make
```bash
make
```
### Using CMake
```bash
mkdir build && cd build
cmake ..
@@ -65,16 +57,32 @@ make
- `nwedav` - WebDAV server binary
- `webdavctl` - Administration CLI tool
## Testing
```bash
mkdir build && cd build
cmake ..
make run_tests
./tests/run_tests
```
### Test Categories
- `run_tests` - Unit tests
- `run_integration_tests` - Integration tests (WebDAV methods, HTTP, auth flows)
- `run_management_tests` - User/quota/CGI management tests
- `run_all_tests` - All tests
```bash
ctest -L unit # Run unit tests
ctest -L integration # Run integration tests
ctest -L all # Run all tests
```
## Installation
```bash
make install
```
Default installation prefix is `/usr/local`. Override with `PREFIX`:
```bash
make install PREFIX=/opt/nwedav
cmake --install . --prefix /usr/local
```
## Configuration
@@ -131,8 +139,6 @@ webdavctl quota get <username> [-d <data_dir>]
## Configuration File
Copy `nwedav.conf.sample` to `nwedav.conf` and modify as needed:
```ini
[server]
port = 8080
@@ -182,6 +188,12 @@ src/
├── concurrency/ # Thread pool, event loop, connection pool
├── platform/ # Platform abstraction layer
└── cli/ # webdavctl tool
tests/
├── unit/ # Unit tests
├── integration/ # Integration tests
├── management/ # Management tests
└── fixtures/ # Test fixtures and helpers
```
## Supported Methods
+3
View File
@@ -117,6 +117,7 @@ typedef enum {
HTTP_200_OK = 200,
HTTP_201_CREATED = 201,
HTTP_204_NO_CONTENT = 204,
HTTP_206_PARTIAL_CONTENT = 206,
HTTP_207_MULTI_STATUS = 207,
HTTP_301_MOVED_PERMANENTLY = 301,
HTTP_302_FOUND = 302,
@@ -130,8 +131,10 @@ typedef enum {
HTTP_412_PRECONDITION_FAILED = 412,
HTTP_413_PAYLOAD_TOO_LARGE = 413,
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_416_RANGE_NOT_SATISFIABLE = 416,
HTTP_423_LOCKED = 423,
HTTP_424_FAILED_DEPENDENCY = 424,
HTTP_500_INTERNAL_ERROR = 500,
HTTP_500_INTERNAL_SERVER_ERROR = 500,
HTTP_501_NOT_IMPLEMENTED = 501,
HTTP_502_BAD_GATEWAY = 502,
+31
View File
@@ -279,9 +279,22 @@ int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user) {
user->display_name[sizeof(user->display_name) - 1] = '\0';
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
strncpy(user->custom_directory, db_user.custom_directory, sizeof(user->custom_directory) - 1);
user->custom_directory[sizeof(user->custom_directory) - 1] = '\0';
user->use_custom_directory = db_user.use_custom_directory;
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
user->cgi_enabled = db_user.cgi_enabled;
user->cgi_timeout = db_user.cgi_timeout;
user->cgi_max_output = db_user.cgi_max_output;
user->webserve_enabled = db_user.webserve_enabled;
user->directory_listing = db_user.directory_listing;
strncpy(user->default_index, db_user.default_index, sizeof(user->default_index) - 1);
user->default_index[sizeof(user->default_index) - 1] = '\0';
user->perm_read = db_user.perm_read;
user->perm_write = db_user.perm_write;
user->perm_execute = db_user.perm_execute;
return NWEDAV_OK;
}
@@ -315,9 +328,22 @@ int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user) {
user->display_name[sizeof(user->display_name) - 1] = '\0';
strncpy(user->home_directory, db_user.home_directory, sizeof(user->home_directory) - 1);
user->home_directory[sizeof(user->home_directory) - 1] = '\0';
strncpy(user->custom_directory, db_user.custom_directory, sizeof(user->custom_directory) - 1);
user->custom_directory[sizeof(user->custom_directory) - 1] = '\0';
user->use_custom_directory = db_user.use_custom_directory;
user->quota_bytes = db_user.quota_bytes;
user->used_bytes = db_user.used_bytes;
user->enabled = db_user.enabled;
user->cgi_enabled = db_user.cgi_enabled;
user->cgi_timeout = db_user.cgi_timeout;
user->cgi_max_output = db_user.cgi_max_output;
user->webserve_enabled = db_user.webserve_enabled;
user->directory_listing = db_user.directory_listing;
strncpy(user->default_index, db_user.default_index, sizeof(user->default_index) - 1);
user->default_index[sizeof(user->default_index) - 1] = '\0';
user->perm_read = db_user.perm_read;
user->perm_write = db_user.perm_write;
user->perm_execute = db_user.perm_execute;
return NWEDAV_OK;
}
@@ -325,6 +351,11 @@ int auth_check(auth_context_t *auth, http_request_t *req, auth_user_t *user) {
return NWEDAV_ERROR_AUTH;
}
sqlite3 *auth_get_db(auth_context_t *auth) {
if (!auth || !auth->usermgr) return NULL;
return user_manager_get_db(auth->usermgr);
}
int auth_build_challenge(auth_context_t *auth, http_response_t *res) {
if (!auth || !res) return NWEDAV_ERROR;
+14
View File
@@ -11,9 +11,20 @@ typedef struct {
char email[256];
char display_name[256];
char home_directory[PATH_MAX];
char custom_directory[PATH_MAX];
int use_custom_directory;
uint64_t quota_bytes;
uint64_t used_bytes;
int enabled;
int cgi_enabled;
int cgi_timeout;
size_t cgi_max_output;
int webserve_enabled;
int directory_listing;
char default_index[64];
int perm_read;
int perm_write;
int perm_execute;
} auth_user_t;
typedef struct auth_context auth_context_t;
@@ -33,4 +44,7 @@ int auth_parse_digest(const char *header, char *username, char *realm,
char *auth_base64_encode(const unsigned char *data, size_t len);
unsigned char *auth_base64_decode(const char *data, size_t *out_len);
struct sqlite3;
struct sqlite3 *auth_get_db(auth_context_t *auth);
#endif
BIN
View File
Binary file not shown.
+522
View File
@@ -0,0 +1,522 @@
#include "cgi.h"
#include "../platform/pal.h"
#include "../platform/file.h"
#include "../platform/memory.h"
#include "../http/streaming.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#endif
void cgi_config_init(cgi_config_t *config) {
if (!config) return;
memset(config, 0, sizeof(cgi_config_t));
config->enabled = 0;
config->timeout_seconds = 30;
config->max_output_bytes = 10 * 1024 * 1024;
config->buffer_size = CGI_OUTPUT_BUFFER_SIZE;
config->server_port = 80;
strncpy(config->server_name, "localhost", sizeof(config->server_name) - 1);
}
int cgi_is_executable(const char *path) {
if (!path) return 0;
#ifdef _WIN32
const char *ext = strrchr(path, '.');
if (ext) {
if (str_casecmp(ext, ".exe") == 0 ||
str_casecmp(ext, ".bat") == 0 ||
str_casecmp(ext, ".cmd") == 0) {
return 1;
}
}
return 0;
#else
return access(path, X_OK) == 0;
#endif
}
int cgi_detect_headers(const char *output, size_t len) {
if (!output || len < 5) return 0;
const char *p = output;
const char *end = output + (len > 1024 ? 1024 : len);
while (p < end) {
const char *line_end = memchr(p, '\n', end - p);
if (!line_end) break;
size_t line_len = line_end - p;
if (line_len > 0 && p[line_len - 1] == '\r') {
line_len--;
}
if (line_len == 0) {
return 1;
}
const char *colon = memchr(p, ':', line_len);
if (!colon) {
if (strncmp(p, "HTTP/", 5) == 0) {
p = line_end + 1;
continue;
}
return 0;
}
size_t name_len = colon - p;
if (name_len == 0 || name_len > 64) return 0;
for (size_t i = 0; i < name_len; i++) {
char c = p[i];
if (!isalnum((unsigned char)c) && c != '-' && c != '_') {
return 0;
}
}
p = line_end + 1;
}
return 0;
}
#ifndef _WIN32
static void safe_setenv(char **envp, int *idx, const char *name, const char *value, size_t max_val_len) {
if (!name || !value || *idx >= CGI_ENV_MAX_COUNT - 1) return;
size_t name_len = strlen(name);
size_t val_len = strlen(value);
if (val_len > max_val_len) val_len = max_val_len;
char *entry = malloc(name_len + val_len + 2);
if (!entry) return;
memcpy(entry, name, name_len);
entry[name_len] = '=';
memcpy(entry + name_len + 1, value, val_len);
entry[name_len + 1 + val_len] = '\0';
envp[*idx] = entry;
(*idx)++;
}
static void free_env(char **envp) {
for (int i = 0; envp[i]; i++) {
free(envp[i]);
}
}
static int parse_cgi_status(const char *line, size_t len) {
while (len > 0 && isspace((unsigned char)*line)) {
line++;
len--;
}
int status = 0;
while (len > 0 && isdigit((unsigned char)*line)) {
status = status * 10 + (*line - '0');
line++;
len--;
}
return (status >= 100 && status < 600) ? status : 200;
}
int cgi_execute(const char *script_path, const char *path_info,
http_request_t *req, http_response_t *res,
socket_t client_fd, cgi_config_t *config) {
if (!script_path || !req || !res || !config) return NWEDAV_ERROR;
int stdin_pipe[2] = {-1, -1};
int stdout_pipe[2] = {-1, -1};
if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0) {
if (stdin_pipe[0] >= 0) close(stdin_pipe[0]);
if (stdin_pipe[1] >= 0) close(stdin_pipe[1]);
if (stdout_pipe[0] >= 0) close(stdout_pipe[0]);
if (stdout_pipe[1] >= 0) close(stdout_pipe[1]);
return NWEDAV_ERROR;
}
pid_t pid = fork();
if (pid < 0) {
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
return NWEDAV_ERROR;
}
if (pid == 0) {
close(stdin_pipe[1]);
close(stdout_pipe[0]);
dup2(stdin_pipe[0], STDIN_FILENO);
dup2(stdout_pipe[1], STDOUT_FILENO);
dup2(stdout_pipe[1], STDERR_FILENO);
close(stdin_pipe[0]);
close(stdout_pipe[1]);
for (int fd = 3; fd < 1024; fd++) {
close(fd);
}
char script_dir[PATH_MAX];
strncpy(script_dir, script_path, sizeof(script_dir) - 1);
script_dir[sizeof(script_dir) - 1] = '\0';
char *last_slash = strrchr(script_dir, '/');
if (last_slash) {
*last_slash = '\0';
chdir(script_dir);
}
char *envp[CGI_ENV_MAX_COUNT];
memset(envp, 0, sizeof(envp));
int env_idx = 0;
safe_setenv(envp, &env_idx, "GATEWAY_INTERFACE", "CGI/1.1", 16);
safe_setenv(envp, &env_idx, "SERVER_PROTOCOL", "HTTP/1.1", 16);
safe_setenv(envp, &env_idx, "SERVER_SOFTWARE", "nwedav/1.0.0", 32);
safe_setenv(envp, &env_idx, "REQUEST_METHOD", http_method_to_string(req->method), 16);
if (req->uri) {
safe_setenv(envp, &env_idx, "REQUEST_URI", req->uri, PATH_MAX);
}
if (path_info) {
safe_setenv(envp, &env_idx, "PATH_INFO", path_info, PATH_MAX);
}
if (req->query_string) {
safe_setenv(envp, &env_idx, "QUERY_STRING", req->query_string, CGI_ENV_MAX_SIZE);
} else {
safe_setenv(envp, &env_idx, "QUERY_STRING", "", 1);
}
safe_setenv(envp, &env_idx, "SCRIPT_FILENAME", script_path, PATH_MAX);
const char *script_name = strrchr(script_path, '/');
if (script_name) {
safe_setenv(envp, &env_idx, "SCRIPT_NAME", script_name, PATH_MAX);
}
if (config->client_ip[0]) {
safe_setenv(envp, &env_idx, "REMOTE_ADDR", config->client_ip, 64);
safe_setenv(envp, &env_idx, "REMOTE_HOST", config->client_ip, 64);
}
if (config->server_name[0]) {
safe_setenv(envp, &env_idx, "SERVER_NAME", config->server_name, 256);
}
char port_str[8];
snprintf(port_str, sizeof(port_str), "%d", config->server_port);
safe_setenv(envp, &env_idx, "SERVER_PORT", port_str, 8);
if (req->content_length > 0) {
char cl_str[32];
snprintf(cl_str, sizeof(cl_str), "%zu", req->content_length);
safe_setenv(envp, &env_idx, "CONTENT_LENGTH", cl_str, 20);
}
if (req->content_type) {
safe_setenv(envp, &env_idx, "CONTENT_TYPE", req->content_type, 256);
}
if (req->host) {
safe_setenv(envp, &env_idx, "HTTP_HOST", req->host, 256);
}
http_header_t *h = req->headers;
while (h && env_idx < CGI_ENV_MAX_COUNT - 1) {
if (str_casecmp(h->name, "Content-Type") != 0 &&
str_casecmp(h->name, "Content-Length") != 0) {
char env_name[128] = "HTTP_";
size_t name_len = strlen(h->name);
if (name_len > sizeof(env_name) - 6) {
name_len = sizeof(env_name) - 6;
}
for (size_t i = 0; i < name_len; i++) {
char c = h->name[i];
if (c == '-') c = '_';
else c = toupper((unsigned char)c);
env_name[5 + i] = c;
}
env_name[5 + name_len] = '\0';
safe_setenv(envp, &env_idx, env_name, h->value, CGI_MAX_HEADER_LINE);
}
h = h->next;
}
envp[env_idx] = NULL;
char *argv[] = {(char *)script_path, NULL};
execve(script_path, argv, envp);
free_env(envp);
_exit(127);
}
close(stdin_pipe[0]);
close(stdout_pipe[1]);
if (req->body && req->content_length > 0) {
size_t written = 0;
while (written < req->content_length) {
ssize_t w = write(stdin_pipe[1], req->body + written, req->content_length - written);
if (w <= 0) break;
written += w;
}
}
close(stdin_pipe[1]);
int flags = fcntl(stdout_pipe[0], F_GETFL, 0);
fcntl(stdout_pipe[0], F_SETFL, flags | O_NONBLOCK);
char *output = malloc(config->buffer_size);
if (!output) {
close(stdout_pipe[0]);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
return NWEDAV_ERROR;
}
size_t total_read = 0;
size_t header_end = 0;
int headers_sent = 0;
int has_valid_headers = 0;
int status_code = 200;
time_t start_time = time(NULL);
while (1) {
if (time(NULL) - start_time > config->timeout_seconds) {
kill(pid, SIGKILL);
break;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(stdout_pipe[0], &rfds);
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
int ret = select(stdout_pipe[0] + 1, &rfds, NULL, NULL, &tv);
if (ret < 0) {
if (errno == EINTR) continue;
break;
}
if (ret == 0) {
int status;
pid_t w = waitpid(pid, &status, WNOHANG);
if (w == pid) break;
continue;
}
size_t space_left = config->buffer_size - total_read - 1;
if (space_left == 0) {
if (!headers_sent) {
has_valid_headers = cgi_detect_headers(output, total_read);
if (has_valid_headers) {
char *header_sep = strstr(output, "\r\n\r\n");
if (!header_sep) header_sep = strstr(output, "\n\n");
if (header_sep) {
header_end = header_sep - output + (header_sep[0] == '\r' ? 4 : 2);
char *status_line = strstr(output, "Status:");
if (status_line && status_line < output + header_end) {
status_code = parse_cgi_status(status_line + 7, header_end - (status_line + 7 - output));
}
http_response_set_status(res, status_code);
char *p = output;
while (p < output + header_end) {
char *line_end = strchr(p, '\n');
if (!line_end) break;
size_t line_len = line_end - p;
if (line_len > 0 && p[line_len - 1] == '\r') line_len--;
if (line_len == 0) break;
char *colon = memchr(p, ':', line_len);
if (colon && strncasecmp(p, "Status", 6) != 0) {
*colon = '\0';
char *val = colon + 1;
while (*val == ' ') val++;
char *val_end = p + line_len;
*val_end = '\0';
http_response_add_header(res, p, val);
}
p = line_end + 1;
}
http_response_add_header(res, "Transfer-Encoding", "chunked");
headers_sent = 1;
char header_buf[4096];
int header_len = http_response_build_headers(res, header_buf, sizeof(header_buf));
if (header_len > 0) {
pal_socket_send(client_fd, header_buf, header_len, 0);
}
if (total_read > header_end) {
streaming_send_chunk(client_fd, output + header_end, total_read - header_end);
}
}
} else {
http_response_set_status(res, 200);
http_response_add_header(res, "Content-Type", "text/plain; charset=utf-8");
http_response_add_header(res, "Transfer-Encoding", "chunked");
headers_sent = 1;
char header_buf[4096];
int header_len = http_response_build_headers(res, header_buf, sizeof(header_buf));
if (header_len > 0) {
pal_socket_send(client_fd, header_buf, header_len, 0);
}
streaming_send_chunk(client_fd, output, total_read);
}
} else {
streaming_send_chunk(client_fd, output, total_read);
}
total_read = 0;
}
ssize_t n = read(stdout_pipe[0], output + total_read, space_left);
if (n <= 0) {
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
continue;
}
break;
}
total_read += n;
if (total_read > config->max_output_bytes) {
kill(pid, SIGKILL);
break;
}
if (!headers_sent && total_read > 4) {
char *header_sep = strstr(output, "\r\n\r\n");
if (!header_sep) header_sep = strstr(output, "\n\n");
if (header_sep) {
has_valid_headers = cgi_detect_headers(output, total_read);
}
}
}
close(stdout_pipe[0]);
int status;
waitpid(pid, &status, 0);
if (total_read > 0) {
if (!headers_sent) {
has_valid_headers = cgi_detect_headers(output, total_read);
if (has_valid_headers) {
char *header_sep = strstr(output, "\r\n\r\n");
if (!header_sep) header_sep = strstr(output, "\n\n");
if (header_sep) {
header_end = header_sep - output + (header_sep[0] == '\r' ? 4 : 2);
char *status_line = strstr(output, "Status:");
if (status_line && status_line < output + header_end) {
status_code = parse_cgi_status(status_line + 7, header_end - (status_line + 7 - output));
}
http_response_set_status(res, status_code);
char *p = output;
while (p < output + header_end) {
char *line_end = strchr(p, '\n');
if (!line_end) break;
size_t line_len = line_end - p;
if (line_len > 0 && p[line_len - 1] == '\r') line_len--;
if (line_len == 0) break;
char *colon = memchr(p, ':', line_len);
if (colon && strncasecmp(p, "Status", 6) != 0) {
*colon = '\0';
char *val = colon + 1;
while (*val == ' ') val++;
char *val_end = p + line_len;
*val_end = '\0';
http_response_add_header(res, p, val);
}
p = line_end + 1;
}
http_response_add_header(res, "Transfer-Encoding", "chunked");
headers_sent = 1;
char header_buf[4096];
int header_len = http_response_build_headers(res, header_buf, sizeof(header_buf));
if (header_len > 0) {
pal_socket_send(client_fd, header_buf, header_len, 0);
}
if (total_read > header_end) {
streaming_send_chunk(client_fd, output + header_end, total_read - header_end);
}
}
} else {
http_response_set_status(res, 200);
http_response_add_header(res, "Content-Type", "text/plain; charset=utf-8");
http_response_add_header(res, "Transfer-Encoding", "chunked");
headers_sent = 1;
char header_buf[4096];
int header_len = http_response_build_headers(res, header_buf, sizeof(header_buf));
if (header_len > 0) {
pal_socket_send(client_fd, header_buf, header_len, 0);
}
streaming_send_chunk(client_fd, output, total_read);
}
} else {
streaming_send_chunk(client_fd, output, total_read);
}
}
if (headers_sent) {
streaming_send_chunked_end(client_fd);
}
free(output);
return NWEDAV_OK;
}
#else
int cgi_execute(const char *script_path, const char *path_info,
http_request_t *req, http_response_t *res,
socket_t client_fd, cgi_config_t *config) {
(void)script_path;
(void)path_info;
(void)req;
(void)config;
(void)client_fd;
http_response_error(res, HTTP_501_NOT_IMPLEMENTED, "CGI not supported on Windows");
return NWEDAV_ERROR;
}
#endif
+34
View File
@@ -0,0 +1,34 @@
#ifndef CGI_H
#define CGI_H
#include "nwedav.h"
#include "../http/parser.h"
#include "../http/response.h"
#define CGI_ENV_MAX_COUNT 64
#define CGI_ENV_MAX_SIZE 8192
#define CGI_HEADER_MAX_SIZE 16384
#define CGI_OUTPUT_BUFFER_SIZE 32768
#define CGI_MAX_HEADER_LINE 4096
typedef struct {
int enabled;
int timeout_seconds;
size_t max_output_bytes;
size_t buffer_size;
char client_ip[64];
char server_name[256];
int server_port;
} cgi_config_t;
void cgi_config_init(cgi_config_t *config);
int cgi_is_executable(const char *path);
int cgi_execute(const char *script_path, const char *path_info,
http_request_t *req, http_response_t *res,
socket_t client_fd, cgi_config_t *config);
int cgi_detect_headers(const char *output, size_t len);
#endif
+745 -29
View File
@@ -1,10 +1,12 @@
#include "../platform/pal.h"
#include "../platform/memory.h"
#include "../user/usermgr.h"
#include "../storage/pathmapping.h"
#include "../core/config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef _WIN32
#include <conio.h>
@@ -22,8 +24,10 @@ static void print_usage(void) {
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(" -h, --help Show this help\n");
printf(" --version Show version\n\n");
printf("Commands:\n");
printf(" init Initialize database and configuration\n\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");
@@ -31,13 +35,42 @@ static void print_usage(void) {
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(" user setdir <name> <path> Set custom directory for user\n");
printf(" user cleardir <name> Clear custom directory (use system default)\n\n");
printf(" user perm set <name> <read|write|execute> Set permission\n");
printf(" user perm clear <name> <read|write|execute> Clear permission\n");
printf(" user perm show <name> Show permissions\n\n");
printf(" user cgi enable <name> Enable CGI execution\n");
printf(" user cgi disable <name> Disable CGI execution\n");
printf(" user cgi timeout <name> <sec> Set CGI timeout\n");
printf(" user cgi maxoutput <name> <bytes> Set max CGI output\n");
printf(" user cgi show <name> Show CGI settings\n\n");
printf(" user web enable <name> Enable web serving\n");
printf(" user web disable <name> Disable web serving\n");
printf(" user web listing enable <name> Enable directory listing\n");
printf(" user web listing disable <name> Disable directory listing\n");
printf(" user web index <name> <file> Set default index file\n");
printf(" user web show <name> Show web serving settings\n\n");
printf(" quota show <username> Show user quota\n");
printf(" quota set <username> <bytes> Set user quota\n");
printf(" quota set <username> <bytes> Set user quota\n\n");
printf(" group add <name> Add a group\n");
printf(" group delete <name> Delete a group\n");
printf(" group list List all groups\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");
printf(" group members <group> List group members\n\n");
printf(" mapping add <url> <fs_path> Add path mapping\n");
printf(" mapping delete <url> Delete path mapping\n");
printf(" mapping list List all mappings\n");
printf(" mapping set <url> <read|execute> Set mapping permission\n");
printf(" mapping web enable <url> Enable web serving\n");
printf(" mapping web disable <url> Disable web serving\n");
printf(" mapping cgi enable <url> Enable CGI execution\n");
printf(" mapping cgi disable <url> Disable CGI execution\n");
printf(" mapping listing enable <url> Enable directory listing\n");
printf(" mapping listing disable <url> Disable directory listing\n");
printf(" mapping index <url> <file> Set default index file\n");
printf(" mapping show <url> Show mapping details\n");
}
static char *read_password(const char *prompt) {
@@ -84,6 +117,42 @@ static char *read_password(const char *prompt) {
return password;
}
static uint64_t parse_size(const char *str) {
char *endptr;
double val = strtod(str, &endptr);
uint64_t multiplier = 1;
while (*endptr == ' ') endptr++;
if (*endptr) {
char unit = toupper((unsigned char)*endptr);
switch (unit) {
case 'K': multiplier = 1024ULL; break;
case 'M': multiplier = 1024ULL * 1024; break;
case 'G': multiplier = 1024ULL * 1024 * 1024; break;
case 'T': multiplier = 1024ULL * 1024 * 1024 * 1024; break;
}
}
return (uint64_t)(val * multiplier);
}
static void format_size(uint64_t bytes, char *buf, size_t buf_size) {
if (bytes == 0) {
snprintf(buf, buf_size, "unlimited");
} else if (bytes >= 1024ULL * 1024 * 1024 * 1024) {
snprintf(buf, buf_size, "%.2f TB", bytes / (1024.0 * 1024 * 1024 * 1024));
} else if (bytes >= 1024ULL * 1024 * 1024) {
snprintf(buf, buf_size, "%.2f GB", bytes / (1024.0 * 1024 * 1024));
} else if (bytes >= 1024ULL * 1024) {
snprintf(buf, buf_size, "%.2f MB", bytes / (1024.0 * 1024));
} else if (bytes >= 1024) {
snprintf(buf, buf_size, "%.2f KB", bytes / 1024.0);
} else {
snprintf(buf, buf_size, "%llu B", (unsigned long long)bytes);
}
}
static int user_list_callback(user_t *user, void *data) {
(void)data;
@@ -95,22 +164,43 @@ static int user_list_callback(user_t *user, void *data) {
(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,
char quota_str[32], used_str[32];
format_size(user->quota_bytes, quota_str, sizeof(quota_str));
format_size(user->used_bytes, used_str, sizeof(used_str));
printf("%-4u %-16s %-8s %12s %12s\n",
user->id, user->username,
user->enabled ? "enabled" : "disabled",
(unsigned long long)user->quota_bytes,
(unsigned long long)user->used_bytes);
quota_str, used_str);
}
return 0;
}
static int group_list_callback(group_t *group, void *data) {
(void)data;
printf("%-4u %-20s %s\n", group->id, group->name, group->description);
return 0;
}
static int mapping_list_callback(path_mapping_t *mapping, void *data) {
(void)data;
printf("%-20s -> %-30s [%s%s] %s %s %s\n",
mapping->url_path,
mapping->filesystem_path,
mapping->permission_read ? "R" : "-",
mapping->permission_execute ? "X" : "-",
mapping->webserve_enabled ? "web" : "---",
mapping->cgi_enabled ? "cgi" : "---",
mapping->directory_listing ? "list" : "----");
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");
printf("%-4s %-16s %-8s %12s %12s\n",
"ID", "Username", "Status", "Quota", "Used");
printf("--------------------------------------------------------------\n");
}
user_manager_list(mgr, user_list_callback, NULL);
@@ -139,11 +229,11 @@ static int cmd_user_add(user_manager_t *mgr, const char *username) {
}
}
printf("Quota in bytes (0 for unlimited): ");
printf("Quota (e.g., 10G, 500M, 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);
quota = parse_size(quota_str);
}
int ret = user_manager_add(mgr, username, password, email[0] ? email : NULL, quota);
@@ -214,6 +304,11 @@ static int cmd_user_info(user_manager_t *mgr, const char *username) {
return 1;
}
char quota_str[32], used_str[32], max_output_str[32];
format_size(user.quota_bytes, quota_str, sizeof(quota_str));
format_size(user.used_bytes, used_str, sizeof(used_str));
format_size(user.cgi_max_output, max_output_str, sizeof(max_output_str));
if (g_json) {
printf("{\n");
printf(" \"id\": %u,\n", user.id);
@@ -221,27 +316,270 @@ static int cmd_user_info(user_manager_t *mgr, const char *username) {
printf(" \"email\": \"%s\",\n", user.email);
printf(" \"display_name\": \"%s\",\n", user.display_name);
printf(" \"home_directory\": \"%s\",\n", user.home_directory);
printf(" \"custom_directory\": \"%s\",\n", user.custom_directory);
printf(" \"use_custom_directory\": %s,\n", user.use_custom_directory ? "true" : "false");
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(" \"perm_read\": %s,\n", user.perm_read ? "true" : "false");
printf(" \"perm_write\": %s,\n", user.perm_write ? "true" : "false");
printf(" \"perm_execute\": %s,\n", user.perm_execute ? "true" : "false");
printf(" \"cgi_enabled\": %s,\n", user.cgi_enabled ? "true" : "false");
printf(" \"cgi_timeout\": %d,\n", user.cgi_timeout);
printf(" \"cgi_max_output\": %zu,\n", user.cgi_max_output);
printf(" \"webserve_enabled\": %s,\n", user.webserve_enabled ? "true" : "false");
printf(" \"directory_listing\": %s,\n", user.directory_listing ? "true" : "false");
printf(" \"default_index\": \"%s\"\n", user.default_index);
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");
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(" Status: %s\n", user.enabled ? "enabled" : "disabled");
printf("\nDirectory:\n");
printf(" Home Directory: %s\n", user.home_directory);
if (user.use_custom_directory) {
printf(" Custom Directory: %s\n", user.custom_directory);
}
printf("\nQuota:\n");
printf(" Quota: %s\n", quota_str);
printf(" Used: %s\n", used_str);
printf("\nPermissions:\n");
printf(" Read: %s\n", user.perm_read ? "yes" : "no");
printf(" Write: %s\n", user.perm_write ? "yes" : "no");
printf(" Execute: %s\n", user.perm_execute ? "yes" : "no");
printf("\nCGI Settings:\n");
printf(" CGI Enabled: %s\n", user.cgi_enabled ? "yes" : "no");
printf(" CGI Timeout: %d seconds\n", user.cgi_timeout);
printf(" CGI Max Output: %s\n", max_output_str);
printf("\nWeb Serving:\n");
printf(" Web Enabled: %s\n", user.webserve_enabled ? "yes" : "no");
printf(" Dir Listing: %s\n", user.directory_listing ? "yes" : "no");
printf(" Default Index: %s\n", user.default_index);
}
return 0;
}
static int cmd_user_setdir(user_manager_t *mgr, const char *username, const char *path) {
int ret = user_manager_set_custom_directory(mgr, username, path);
if (ret == NWEDAV_OK) {
printf("Custom directory set to '%s' for user '%s'\n", path, username);
return 0;
} else {
fprintf(stderr, "Failed to set custom directory for user '%s'\n", username);
return 1;
}
}
static int cmd_user_cleardir(user_manager_t *mgr, const char *username) {
int ret = user_manager_clear_custom_directory(mgr, username);
if (ret == NWEDAV_OK) {
printf("Custom directory cleared for user '%s'\n", username);
return 0;
} else {
fprintf(stderr, "Failed to clear custom directory for user '%s'\n", username);
return 1;
}
}
static int cmd_user_perm_set(user_manager_t *mgr, const char *username, const char *perm) {
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;
}
int perm_read = user.perm_read;
int perm_write = user.perm_write;
int perm_execute = user.perm_execute;
if (strcmp(perm, "read") == 0) {
perm_read = 1;
} else if (strcmp(perm, "write") == 0) {
perm_write = 1;
perm_read = 1;
} else if (strcmp(perm, "execute") == 0) {
perm_execute = 1;
} else {
fprintf(stderr, "Unknown permission: %s (use read, write, or execute)\n", perm);
return 1;
}
ret = user_manager_set_permission(mgr, username, perm_read, perm_write, perm_execute);
if (ret == NWEDAV_OK) {
printf("Permission '%s' set for user '%s'\n", perm, username);
return 0;
} else {
fprintf(stderr, "Failed to set permission\n");
return 1;
}
}
static int cmd_user_perm_clear(user_manager_t *mgr, const char *username, const char *perm) {
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;
}
int perm_read = user.perm_read;
int perm_write = user.perm_write;
int perm_execute = user.perm_execute;
if (strcmp(perm, "read") == 0) {
perm_read = 0;
} else if (strcmp(perm, "write") == 0) {
perm_write = 0;
} else if (strcmp(perm, "execute") == 0) {
perm_execute = 0;
} else {
fprintf(stderr, "Unknown permission: %s\n", perm);
return 1;
}
ret = user_manager_set_permission(mgr, username, perm_read, perm_write, perm_execute);
if (ret == NWEDAV_OK) {
printf("Permission '%s' cleared for user '%s'\n", perm, username);
return 0;
} else {
fprintf(stderr, "Failed to clear permission\n");
return 1;
}
}
static int cmd_user_perm_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;
}
printf("Permissions for user '%s':\n", username);
printf(" Read: %s\n", user.perm_read ? "yes" : "no");
printf(" Write: %s\n", user.perm_write ? "yes" : "no");
printf(" Execute: %s\n", user.perm_execute ? "yes" : "no");
return 0;
}
static int cmd_user_cgi_enable(user_manager_t *mgr, const char *username, int enable) {
int ret = user_manager_set_cgi_enabled(mgr, username, enable);
if (ret == NWEDAV_OK) {
printf("CGI %s for user '%s'\n", enable ? "enabled" : "disabled", username);
return 0;
} else {
fprintf(stderr, "Failed to %s CGI for user '%s'\n", enable ? "enable" : "disable", username);
return 1;
}
}
static int cmd_user_cgi_timeout(user_manager_t *mgr, const char *username, const char *timeout_str) {
int timeout = atoi(timeout_str);
if (timeout <= 0) {
fprintf(stderr, "Invalid timeout value\n");
return 1;
}
int ret = user_manager_set_cgi_timeout(mgr, username, timeout);
if (ret == NWEDAV_OK) {
printf("CGI timeout set to %d seconds for user '%s'\n", timeout, username);
return 0;
} else {
fprintf(stderr, "Failed to set CGI timeout\n");
return 1;
}
}
static int cmd_user_cgi_maxoutput(user_manager_t *mgr, const char *username, const char *size_str) {
size_t max_output = parse_size(size_str);
if (max_output == 0) {
fprintf(stderr, "Invalid size value\n");
return 1;
}
int ret = user_manager_set_cgi_max_output(mgr, username, max_output);
if (ret == NWEDAV_OK) {
char size_buf[32];
format_size(max_output, size_buf, sizeof(size_buf));
printf("CGI max output set to %s for user '%s'\n", size_buf, username);
return 0;
} else {
fprintf(stderr, "Failed to set CGI max output\n");
return 1;
}
}
static int cmd_user_cgi_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;
}
char max_output_str[32];
format_size(user.cgi_max_output, max_output_str, sizeof(max_output_str));
printf("CGI settings for user '%s':\n", username);
printf(" Enabled: %s\n", user.cgi_enabled ? "yes" : "no");
printf(" Timeout: %d seconds\n", user.cgi_timeout);
printf(" Max Output: %s\n", max_output_str);
return 0;
}
static int cmd_user_web_enable(user_manager_t *mgr, const char *username, int enable) {
int ret = user_manager_set_webserve_enabled(mgr, username, enable);
if (ret == NWEDAV_OK) {
printf("Web serving %s for user '%s'\n", enable ? "enabled" : "disabled", username);
return 0;
} else {
fprintf(stderr, "Failed to %s web serving\n", enable ? "enable" : "disable");
return 1;
}
}
static int cmd_user_web_listing(user_manager_t *mgr, const char *username, int enable) {
int ret = user_manager_set_directory_listing(mgr, username, enable);
if (ret == NWEDAV_OK) {
printf("Directory listing %s for user '%s'\n", enable ? "enabled" : "disabled", username);
return 0;
} else {
fprintf(stderr, "Failed to %s directory listing\n", enable ? "enable" : "disable");
return 1;
}
}
static int cmd_user_web_index(user_manager_t *mgr, const char *username, const char *index) {
int ret = user_manager_set_default_index(mgr, username, index);
if (ret == NWEDAV_OK) {
printf("Default index set to '%s' for user '%s'\n", index, username);
return 0;
} else {
fprintf(stderr, "Failed to set default index\n");
return 1;
}
}
static int cmd_user_web_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;
}
printf("Web serving settings for user '%s':\n", username);
printf(" Enabled: %s\n", user.webserve_enabled ? "yes" : "no");
printf(" Dir Listing: %s\n", user.directory_listing ? "yes" : "no");
printf(" Default Index: %s\n", user.default_index);
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);
@@ -250,14 +588,18 @@ static int cmd_quota_show(user_manager_t *mgr, const char *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);
char quota_str[32], used_str[32];
format_size(user.quota_bytes, quota_str, sizeof(quota_str));
format_size(user.used_bytes, used_str, sizeof(used_str));
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);
printf(" Quota: %s\n", quota_str);
printf(" Used: %s\n", used_str);
if (user.quota_bytes > 0) {
printf(" Usage: %.1f%%\n", percent);
}
return 0;
}
@@ -270,10 +612,12 @@ static int cmd_quota_set(user_manager_t *mgr, const char *username, const char *
return 1;
}
uint64_t quota = strtoull(quota_str, NULL, 10);
uint64_t quota = parse_size(quota_str);
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);
char size_buf[32];
format_size(quota, size_buf, sizeof(size_buf));
printf("Quota set to %s for user '%s'\n", size_buf, username);
return 0;
} else {
fprintf(stderr, "Failed to set quota\n");
@@ -303,6 +647,13 @@ static int cmd_group_delete(user_manager_t *mgr, const char *name) {
}
}
static int cmd_group_list(user_manager_t *mgr) {
printf("%-4s %-20s %s\n", "ID", "Name", "Description");
printf("----------------------------------------\n");
user_manager_group_list(mgr, group_list_callback, NULL);
return 0;
}
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) {
@@ -325,6 +676,140 @@ static int cmd_group_removeuser(user_manager_t *mgr, const char *group, const ch
}
}
static int cmd_group_members(user_manager_t *mgr, const char *group_name) {
printf("Members of group '%s':\n", group_name);
printf("%-4s %-16s %-8s\n", "ID", "Username", "Status");
printf("--------------------------------\n");
user_manager_group_members(mgr, group_name, user_list_callback, NULL);
return 0;
}
static int cmd_mapping_add(path_mapping_manager_t *map_mgr, const char *url, const char *fs_path) {
path_mapping_t mapping;
memset(&mapping, 0, sizeof(mapping));
strncpy(mapping.url_path, url, sizeof(mapping.url_path) - 1);
strncpy(mapping.filesystem_path, fs_path, sizeof(mapping.filesystem_path) - 1);
mapping.permission_read = 1;
mapping.permission_execute = 0;
mapping.webserve_enabled = 1;
mapping.cgi_enabled = 0;
mapping.directory_listing = 1;
strncpy(mapping.default_index, "index.html", sizeof(mapping.default_index) - 1);
int ret = pathmapping_add(map_mgr, &mapping);
if (ret == NWEDAV_OK) {
printf("Mapping added: %s -> %s\n", url, fs_path);
return 0;
} else {
fprintf(stderr, "Failed to add mapping\n");
return 1;
}
}
static int cmd_mapping_delete(path_mapping_manager_t *map_mgr, const char *url) {
int ret = pathmapping_delete(map_mgr, url);
if (ret == NWEDAV_OK) {
printf("Mapping deleted: %s\n", url);
return 0;
} else {
fprintf(stderr, "Failed to delete mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_list(path_mapping_manager_t *map_mgr) {
printf("%-20s -> %-30s [Perm] Web CGI List\n", "URL", "Filesystem Path");
printf("------------------------------------------------------------------------------\n");
pathmapping_list(map_mgr, mapping_list_callback, NULL);
return 0;
}
static int cmd_mapping_set(path_mapping_manager_t *map_mgr, const char *url, const char *perm) {
int read = 0, execute = 0;
if (strcmp(perm, "read") == 0) {
read = 1;
} else if (strcmp(perm, "execute") == 0) {
execute = 1;
} else {
fprintf(stderr, "Unknown permission: %s (use read or execute)\n", perm);
return 1;
}
int ret = pathmapping_set_permission(map_mgr, url, read, execute);
if (ret == NWEDAV_OK) {
printf("Permission set to '%s' for mapping '%s'\n", perm, url);
return 0;
} else {
fprintf(stderr, "Failed to set permission for mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_web(path_mapping_manager_t *map_mgr, const char *url, int enable) {
int ret = pathmapping_set_webserve(map_mgr, url, enable);
if (ret == NWEDAV_OK) {
printf("Web serving %s for mapping '%s'\n", enable ? "enabled" : "disabled", url);
return 0;
} else {
fprintf(stderr, "Failed to update mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_cgi(path_mapping_manager_t *map_mgr, const char *url, int enable) {
int ret = pathmapping_set_cgi(map_mgr, url, enable);
if (ret == NWEDAV_OK) {
printf("CGI %s for mapping '%s'\n", enable ? "enabled" : "disabled", url);
return 0;
} else {
fprintf(stderr, "Failed to update mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_listing(path_mapping_manager_t *map_mgr, const char *url, int enable) {
int ret = pathmapping_set_listing(map_mgr, url, enable);
if (ret == NWEDAV_OK) {
printf("Directory listing %s for mapping '%s'\n", enable ? "enabled" : "disabled", url);
return 0;
} else {
fprintf(stderr, "Failed to update mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_index(path_mapping_manager_t *map_mgr, const char *url, const char *index) {
int ret = pathmapping_set_index(map_mgr, url, index);
if (ret == NWEDAV_OK) {
printf("Default index set to '%s' for mapping '%s'\n", index, url);
return 0;
} else {
fprintf(stderr, "Failed to update mapping '%s'\n", url);
return 1;
}
}
static int cmd_mapping_show(path_mapping_manager_t *map_mgr, const char *url) {
path_mapping_t mapping;
int ret = pathmapping_get(map_mgr, url, &mapping);
if (ret != NWEDAV_OK) {
fprintf(stderr, "Mapping '%s' not found\n", url);
return 1;
}
printf("Mapping details for '%s':\n", url);
printf(" URL Path: %s\n", mapping.url_path);
printf(" Filesystem Path: %s\n", mapping.filesystem_path);
printf(" Read Permission: %s\n", mapping.permission_read ? "yes" : "no");
printf(" Exec Permission: %s\n", mapping.permission_execute ? "yes" : "no");
printf(" Web Serving: %s\n", mapping.webserve_enabled ? "yes" : "no");
printf(" CGI Enabled: %s\n", mapping.cgi_enabled ? "yes" : "no");
printf(" Dir Listing: %s\n", mapping.directory_listing ? "yes" : "no");
printf(" Default Index: %s\n", mapping.default_index);
return 0;
}
static int cmd_init(void) {
user_manager_t *mgr = user_manager_create(g_db_path);
if (!mgr) {
@@ -354,6 +839,9 @@ int main(int argc, char **argv) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage();
return 0;
} else if (strcmp(argv[i], "--version") == 0) {
printf("webdavctl version %s\n", NWEDAV_VERSION_STRING);
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);
@@ -386,6 +874,8 @@ int main(int argc, char **argv) {
return 1;
}
path_mapping_manager_t *map_mgr = pathmapping_create(user_manager_get_db(mgr));
int ret = 1;
if (strcmp(command, "user") == 0) {
@@ -432,6 +922,132 @@ int main(int argc, char **argv) {
} else {
ret = cmd_user_info(mgr, argv[i]);
}
} else if (strcmp(subcommand, "setdir") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or path\n");
} else {
ret = cmd_user_setdir(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "cleardir") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_cleardir(mgr, argv[i]);
}
} else if (strcmp(subcommand, "perm") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing perm subcommand\n");
} else {
const char *perm_cmd = argv[i++];
if (strcmp(perm_cmd, "set") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or permission\n");
} else {
ret = cmd_user_perm_set(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(perm_cmd, "clear") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or permission\n");
} else {
ret = cmd_user_perm_clear(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(perm_cmd, "show") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_perm_show(mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown perm subcommand: %s\n", perm_cmd);
}
}
} else if (strcmp(subcommand, "cgi") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing cgi subcommand\n");
} else {
const char *cgi_cmd = argv[i++];
if (strcmp(cgi_cmd, "enable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_cgi_enable(mgr, argv[i], 1);
}
} else if (strcmp(cgi_cmd, "disable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_cgi_enable(mgr, argv[i], 0);
}
} else if (strcmp(cgi_cmd, "timeout") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or timeout\n");
} else {
ret = cmd_user_cgi_timeout(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(cgi_cmd, "maxoutput") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or size\n");
} else {
ret = cmd_user_cgi_maxoutput(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(cgi_cmd, "show") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_cgi_show(mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown cgi subcommand: %s\n", cgi_cmd);
}
}
} else if (strcmp(subcommand, "web") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing web subcommand\n");
} else {
const char *web_cmd = argv[i++];
if (strcmp(web_cmd, "enable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_web_enable(mgr, argv[i], 1);
}
} else if (strcmp(web_cmd, "disable") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_web_enable(mgr, argv[i], 0);
}
} else if (strcmp(web_cmd, "listing") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing enable/disable\n");
} else {
const char *listing_cmd = argv[i++];
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else if (strcmp(listing_cmd, "enable") == 0) {
ret = cmd_user_web_listing(mgr, argv[i], 1);
} else if (strcmp(listing_cmd, "disable") == 0) {
ret = cmd_user_web_listing(mgr, argv[i], 0);
} else {
fprintf(stderr, "Unknown listing command: %s\n", listing_cmd);
}
}
} else if (strcmp(web_cmd, "index") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing username or filename\n");
} else {
ret = cmd_user_web_index(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(web_cmd, "show") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing username\n");
} else {
ret = cmd_user_web_show(mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown web subcommand: %s\n", web_cmd);
}
}
} else {
fprintf(stderr, "Unknown user subcommand: %s\n", subcommand);
}
@@ -476,6 +1092,8 @@ int main(int argc, char **argv) {
} else {
ret = cmd_group_delete(mgr, argv[i]);
}
} else if (strcmp(subcommand, "list") == 0) {
ret = cmd_group_list(mgr);
} else if (strcmp(subcommand, "adduser") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing group name or username\n");
@@ -488,15 +1106,113 @@ int main(int argc, char **argv) {
} else {
ret = cmd_group_removeuser(mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "members") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing group name\n");
} else {
ret = cmd_group_members(mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown group subcommand: %s\n", subcommand);
}
}
} else if (strcmp(command, "mapping") == 0) {
if (!map_mgr) {
fprintf(stderr, "Failed to initialize path mapping manager\n");
} else if (i >= argc) {
fprintf(stderr, "Missing mapping subcommand\n");
} else {
const char *subcommand = argv[i++];
if (strcmp(subcommand, "add") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing URL or filesystem path\n");
} else {
ret = cmd_mapping_add(map_mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "delete") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing URL\n");
} else {
ret = cmd_mapping_delete(map_mgr, argv[i]);
}
} else if (strcmp(subcommand, "list") == 0) {
ret = cmd_mapping_list(map_mgr);
} else if (strcmp(subcommand, "set") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing URL or permission\n");
} else {
ret = cmd_mapping_set(map_mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "web") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing enable/disable\n");
} else {
const char *web_cmd = argv[i++];
if (i >= argc) {
fprintf(stderr, "Missing URL\n");
} else if (strcmp(web_cmd, "enable") == 0) {
ret = cmd_mapping_web(map_mgr, argv[i], 1);
} else if (strcmp(web_cmd, "disable") == 0) {
ret = cmd_mapping_web(map_mgr, argv[i], 0);
} else {
fprintf(stderr, "Unknown web command: %s\n", web_cmd);
}
}
} else if (strcmp(subcommand, "cgi") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing enable/disable\n");
} else {
const char *cgi_cmd = argv[i++];
if (i >= argc) {
fprintf(stderr, "Missing URL\n");
} else if (strcmp(cgi_cmd, "enable") == 0) {
ret = cmd_mapping_cgi(map_mgr, argv[i], 1);
} else if (strcmp(cgi_cmd, "disable") == 0) {
ret = cmd_mapping_cgi(map_mgr, argv[i], 0);
} else {
fprintf(stderr, "Unknown cgi command: %s\n", cgi_cmd);
}
}
} else if (strcmp(subcommand, "listing") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing enable/disable\n");
} else {
const char *listing_cmd = argv[i++];
if (i >= argc) {
fprintf(stderr, "Missing URL\n");
} else if (strcmp(listing_cmd, "enable") == 0) {
ret = cmd_mapping_listing(map_mgr, argv[i], 1);
} else if (strcmp(listing_cmd, "disable") == 0) {
ret = cmd_mapping_listing(map_mgr, argv[i], 0);
} else {
fprintf(stderr, "Unknown listing command: %s\n", listing_cmd);
}
}
} else if (strcmp(subcommand, "index") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing URL or filename\n");
} else {
ret = cmd_mapping_index(map_mgr, argv[i], argv[i + 1]);
}
} else if (strcmp(subcommand, "show") == 0) {
if (i >= argc) {
fprintf(stderr, "Missing URL\n");
} else {
ret = cmd_mapping_show(map_mgr, argv[i]);
}
} else {
fprintf(stderr, "Unknown mapping subcommand: %s\n", subcommand);
}
}
} else {
fprintf(stderr, "Unknown command: %s\n", command);
print_usage();
}
if (map_mgr) {
pathmapping_destroy(map_mgr);
}
user_manager_destroy(mgr);
return ret;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+381
View File
@@ -0,0 +1,381 @@
#include "config.h"
#include "logging.h"
#include "../platform/pal.h"
#include "../platform/file.h"
#include "../platform/memory.h"
#include <string.h>
#include <ctype.h>
#include <errno.h>
void config_set_defaults(config_t *config) {
memset(config, 0, sizeof(config_t));
strncpy(config->bind_address, "0.0.0.0", sizeof(config->bind_address) - 1);
config->port = NWEDAV_DEFAULT_PORT;
config->ssl_port = NWEDAV_DEFAULT_SSL_PORT;
config->max_connections = NWEDAV_MAX_CONNECTIONS;
config->connection_timeout = NWEDAV_DEFAULT_TIMEOUT;
config->keepalive_timeout = NWEDAV_DEFAULT_KEEPALIVE;
config->max_request_size = NWEDAV_MAX_REQUEST_SIZE;
config->worker_threads = 0;
config->ssl.enabled = 0;
strncpy(config->ssl.min_protocol, "TLSv1.2", sizeof(config->ssl.min_protocol) - 1);
strncpy(config->ssl.ciphers, "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384", sizeof(config->ssl.ciphers) - 1);
strncpy(config->storage.root_path, "./data", sizeof(config->storage.root_path) - 1);
strncpy(config->storage.temp_path, "./tmp", sizeof(config->storage.temp_path) - 1);
strncpy(config->storage.database_path, "./nwedav.db", sizeof(config->storage.database_path) - 1);
strncpy(config->storage.properties_path, "./properties.db", sizeof(config->storage.properties_path) - 1);
strncpy(config->storage.locks_path, "./locks.db", sizeof(config->storage.locks_path) - 1);
config->auth.method = AUTH_METHOD_BASIC;
strncpy(config->auth.realm, "WebDAV Server", sizeof(config->auth.realm) - 1);
config->auth.session_timeout = 3600;
config->auth.max_failed_attempts = 5;
config->auth.lockout_duration = 900;
config->quota.default_quota = 10ULL * 1024 * 1024 * 1024;
config->quota.enforce = 1;
config->quota.warning_threshold = 90;
config->logging.level = LOG_LEVEL_INFO;
config->logging.max_size = 100 * 1024 * 1024;
config->logging.rotate_count = 10;
config->scaling.enabled = 1;
config->scaling.min_threads = 4;
config->scaling.max_threads = 64;
config->scaling.scale_up_threshold = 10;
config->scaling.scale_down_threshold = 2;
config->scaling.scale_up_step = 2;
config->scaling.scale_down_step = 1;
config->scaling.idle_timeout_ms = 30000;
config->scaling.check_interval_ms = 1000;
config->streaming.read_buffer_size = 32768;
config->streaming.write_buffer_size = 65536;
config->streaming.sendfile_threshold = 16384;
config->streaming.chunk_size = 8192;
config->streaming.max_memory_per_request = 10 * 1024 * 1024;
config->defaults.default_cgi_enabled = 0;
config->defaults.default_cgi_timeout = 30;
config->defaults.default_cgi_max_output = 10 * 1024 * 1024;
config->defaults.default_webserve_enabled = 0;
config->defaults.default_directory_listing = 0;
strncpy(config->defaults.default_index, "index.html", sizeof(config->defaults.default_index) - 1);
config->defaults.default_perm_read = 1;
config->defaults.default_perm_write = 1;
config->defaults.default_perm_execute = 0;
}
static char *trim(char *str) {
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;
}
static int parse_bool(const char *value) {
if (str_casecmp(value, "true") == 0 ||
str_casecmp(value, "yes") == 0 ||
str_casecmp(value, "1") == 0 ||
str_casecmp(value, "on") == 0) {
return 1;
}
return 0;
}
static uint64_t parse_size(const char *value) {
char *end;
errno = 0;
uint64_t size = strtoull(value, &end, 10);
if (errno == ERANGE) return 0;
while (isspace((unsigned char)*end)) end++;
uint64_t multiplier = 1;
if (*end == 'K' || *end == 'k') multiplier = 1024ULL;
else if (*end == 'M' || *end == 'm') multiplier = 1024ULL * 1024;
else if (*end == 'G' || *end == 'g') multiplier = 1024ULL * 1024 * 1024;
else if (*end == 'T' || *end == 't') multiplier = 1024ULL * 1024 * 1024 * 1024;
if (size > UINT64_MAX / multiplier) return 0;
return size * multiplier;
}
static log_level_t parse_log_level(const char *value) {
if (str_casecmp(value, "trace") == 0) return LOG_LEVEL_TRACE;
if (str_casecmp(value, "debug") == 0) return LOG_LEVEL_DEBUG;
if (str_casecmp(value, "info") == 0) return LOG_LEVEL_INFO;
if (str_casecmp(value, "warn") == 0 || str_casecmp(value, "warning") == 0) return LOG_LEVEL_WARN;
if (str_casecmp(value, "error") == 0) return LOG_LEVEL_ERROR;
if (str_casecmp(value, "fatal") == 0) return LOG_LEVEL_FATAL;
return LOG_LEVEL_INFO;
}
static auth_method_t parse_auth_method(const char *value) {
if (str_casecmp(value, "none") == 0) return AUTH_METHOD_NONE;
if (str_casecmp(value, "basic") == 0) return AUTH_METHOD_BASIC;
if (str_casecmp(value, "digest") == 0) return AUTH_METHOD_DIGEST;
return AUTH_METHOD_BASIC;
}
int config_load(const char *path, config_t *config) {
config_set_defaults(config);
if (!path || !path[0]) {
return NWEDAV_OK;
}
FILE *f = fopen(path, "r");
if (!f) {
return NWEDAV_ERROR_CONFIG;
}
char line[1024];
char section[64] = "";
while (fgets(line, sizeof(line), f)) {
char *p = trim(line);
if (*p == '\0' || *p == '#' || *p == ';') {
continue;
}
if (*p == '[') {
char *end = strchr(p, ']');
if (end) {
*end = '\0';
strncpy(section, p + 1, sizeof(section) - 1);
section[sizeof(section) - 1] = '\0';
}
continue;
}
char *eq = strchr(p, '=');
if (!eq) continue;
*eq = '\0';
char *key = trim(p);
char *value = trim(eq + 1);
if (strcmp(section, "server") == 0) {
if (strcmp(key, "bind_address") == 0) {
strncpy(config->bind_address, value, sizeof(config->bind_address) - 1);
} else if (strcmp(key, "port") == 0) {
config->port = (uint16_t)atoi(value);
} else if (strcmp(key, "ssl_port") == 0) {
config->ssl_port = (uint16_t)atoi(value);
} else if (strcmp(key, "max_connections") == 0) {
config->max_connections = (size_t)atoi(value);
} else if (strcmp(key, "connection_timeout") == 0) {
config->connection_timeout = atoi(value);
} else if (strcmp(key, "keepalive_timeout") == 0) {
config->keepalive_timeout = atoi(value);
} else if (strcmp(key, "max_request_size") == 0) {
config->max_request_size = parse_size(value);
} else if (strcmp(key, "worker_threads") == 0) {
config->worker_threads = atoi(value);
}
} else if (strcmp(section, "ssl") == 0) {
if (strcmp(key, "enabled") == 0) {
config->ssl.enabled = parse_bool(value);
} else if (strcmp(key, "cert_file") == 0) {
strncpy(config->ssl.cert_file, value, sizeof(config->ssl.cert_file) - 1);
} else if (strcmp(key, "key_file") == 0) {
strncpy(config->ssl.key_file, value, sizeof(config->ssl.key_file) - 1);
} else if (strcmp(key, "ca_file") == 0) {
strncpy(config->ssl.ca_file, value, sizeof(config->ssl.ca_file) - 1);
} else if (strcmp(key, "verify_client") == 0) {
config->ssl.verify_client = parse_bool(value);
} else if (strcmp(key, "min_protocol") == 0) {
strncpy(config->ssl.min_protocol, value, sizeof(config->ssl.min_protocol) - 1);
} else if (strcmp(key, "ciphers") == 0) {
strncpy(config->ssl.ciphers, value, sizeof(config->ssl.ciphers) - 1);
}
} else if (strcmp(section, "storage") == 0) {
if (strcmp(key, "root_path") == 0) {
strncpy(config->storage.root_path, value, sizeof(config->storage.root_path) - 1);
} else if (strcmp(key, "temp_path") == 0) {
strncpy(config->storage.temp_path, value, sizeof(config->storage.temp_path) - 1);
} else if (strcmp(key, "database_path") == 0) {
strncpy(config->storage.database_path, value, sizeof(config->storage.database_path) - 1);
} else if (strcmp(key, "properties_path") == 0) {
strncpy(config->storage.properties_path, value, sizeof(config->storage.properties_path) - 1);
} else if (strcmp(key, "locks_path") == 0) {
strncpy(config->storage.locks_path, value, sizeof(config->storage.locks_path) - 1);
}
} else if (strcmp(section, "auth") == 0) {
if (strcmp(key, "method") == 0) {
config->auth.method = parse_auth_method(value);
} else if (strcmp(key, "realm") == 0) {
strncpy(config->auth.realm, value, sizeof(config->auth.realm) - 1);
} else if (strcmp(key, "session_timeout") == 0) {
config->auth.session_timeout = atoi(value);
} else if (strcmp(key, "max_failed_attempts") == 0) {
config->auth.max_failed_attempts = atoi(value);
} else if (strcmp(key, "lockout_duration") == 0) {
config->auth.lockout_duration = atoi(value);
}
} else if (strcmp(section, "quota") == 0) {
if (strcmp(key, "default_quota") == 0) {
config->quota.default_quota = parse_size(value);
} else if (strcmp(key, "enforce") == 0) {
config->quota.enforce = parse_bool(value);
} else if (strcmp(key, "warning_threshold") == 0) {
config->quota.warning_threshold = atoi(value);
}
} else if (strcmp(section, "logging") == 0) {
if (strcmp(key, "level") == 0) {
config->logging.level = parse_log_level(value);
} else if (strcmp(key, "file") == 0) {
strncpy(config->logging.file, value, sizeof(config->logging.file) - 1);
} else if (strcmp(key, "access_log") == 0) {
strncpy(config->logging.access_log, value, sizeof(config->logging.access_log) - 1);
} else if (strcmp(key, "max_size") == 0) {
config->logging.max_size = parse_size(value);
} else if (strcmp(key, "rotate_count") == 0) {
config->logging.rotate_count = atoi(value);
}
} else if (strcmp(section, "scaling") == 0) {
if (strcmp(key, "enabled") == 0) {
config->scaling.enabled = parse_bool(value);
} else if (strcmp(key, "min_threads") == 0) {
config->scaling.min_threads = (size_t)atoi(value);
} else if (strcmp(key, "max_threads") == 0) {
config->scaling.max_threads = (size_t)atoi(value);
} else if (strcmp(key, "scale_up_threshold") == 0) {
config->scaling.scale_up_threshold = (size_t)atoi(value);
} else if (strcmp(key, "scale_down_threshold") == 0) {
config->scaling.scale_down_threshold = (size_t)atoi(value);
} else if (strcmp(key, "scale_up_step") == 0) {
config->scaling.scale_up_step = atoi(value);
} else if (strcmp(key, "scale_down_step") == 0) {
config->scaling.scale_down_step = atoi(value);
} else if (strcmp(key, "idle_timeout_ms") == 0) {
config->scaling.idle_timeout_ms = atoi(value);
} else if (strcmp(key, "check_interval_ms") == 0) {
config->scaling.check_interval_ms = atoi(value);
}
} else if (strcmp(section, "streaming") == 0) {
if (strcmp(key, "read_buffer_size") == 0) {
config->streaming.read_buffer_size = parse_size(value);
} else if (strcmp(key, "write_buffer_size") == 0) {
config->streaming.write_buffer_size = parse_size(value);
} else if (strcmp(key, "sendfile_threshold") == 0) {
config->streaming.sendfile_threshold = parse_size(value);
} else if (strcmp(key, "chunk_size") == 0) {
config->streaming.chunk_size = parse_size(value);
} else if (strcmp(key, "max_memory_per_request") == 0) {
config->streaming.max_memory_per_request = parse_size(value);
}
} else if (strcmp(section, "defaults") == 0) {
if (strcmp(key, "cgi_enabled") == 0) {
config->defaults.default_cgi_enabled = parse_bool(value);
} else if (strcmp(key, "cgi_timeout") == 0) {
config->defaults.default_cgi_timeout = atoi(value);
} else if (strcmp(key, "cgi_max_output") == 0) {
config->defaults.default_cgi_max_output = parse_size(value);
} else if (strcmp(key, "webserve_enabled") == 0) {
config->defaults.default_webserve_enabled = parse_bool(value);
} else if (strcmp(key, "directory_listing") == 0) {
config->defaults.default_directory_listing = parse_bool(value);
} else if (strcmp(key, "default_index") == 0) {
strncpy(config->defaults.default_index, value, sizeof(config->defaults.default_index) - 1);
} else if (strcmp(key, "perm_read") == 0) {
config->defaults.default_perm_read = parse_bool(value);
} else if (strcmp(key, "perm_write") == 0) {
config->defaults.default_perm_write = parse_bool(value);
} else if (strcmp(key, "perm_execute") == 0) {
config->defaults.default_perm_execute = parse_bool(value);
}
}
}
fclose(f);
return NWEDAV_OK;
}
int config_validate(config_t *config) {
if (config->port == 0) {
return NWEDAV_ERROR_CONFIG;
}
if (config->max_connections == 0) {
config->max_connections = NWEDAV_MAX_CONNECTIONS;
}
if (config->worker_threads == 0) {
config->worker_threads = pal_get_cpu_count() * 2;
if (config->worker_threads < 4) config->worker_threads = 4;
if (config->worker_threads > 64) config->worker_threads = 64;
}
if (config->storage.root_path[0] == '\0') {
return NWEDAV_ERROR_CONFIG;
}
if (config->scaling.enabled) {
if (config->scaling.min_threads == 0) {
config->scaling.min_threads = 2;
}
if (config->scaling.max_threads == 0) {
config->scaling.max_threads = (size_t)config->worker_threads * 4;
}
if (config->scaling.max_threads < config->scaling.min_threads) {
config->scaling.max_threads = config->scaling.min_threads;
}
if (config->scaling.scale_up_step <= 0) {
config->scaling.scale_up_step = 2;
}
if (config->scaling.scale_down_step <= 0) {
config->scaling.scale_down_step = 1;
}
if (config->scaling.idle_timeout_ms <= 0) {
config->scaling.idle_timeout_ms = 30000;
}
if (config->scaling.check_interval_ms <= 0) {
config->scaling.check_interval_ms = 1000;
}
}
return NWEDAV_OK;
}
void config_dump(config_t *config) {
printf("Server Configuration:\n");
printf(" bind_address: %s\n", config->bind_address);
printf(" port: %u\n", config->port);
printf(" ssl_port: %u\n", config->ssl_port);
printf(" max_connections: %zu\n", config->max_connections);
printf(" worker_threads: %d\n", config->worker_threads);
printf(" storage.root_path: %s\n", config->storage.root_path);
printf(" storage.database_path: %s\n", config->storage.database_path);
printf(" auth.method: %d\n", config->auth.method);
printf(" auth.realm: %s\n", config->auth.realm);
printf(" quota.default_quota: %llu\n", (unsigned long long)config->quota.default_quota);
printf(" quota.enforce: %d\n", config->quota.enforce);
printf(" scaling.enabled: %d\n", config->scaling.enabled);
printf(" scaling.min_threads: %zu\n", config->scaling.min_threads);
printf(" scaling.max_threads: %zu\n", config->scaling.max_threads);
printf(" scaling.scale_up_threshold: %zu\n", config->scaling.scale_up_threshold);
printf(" scaling.scale_down_threshold: %zu\n", config->scaling.scale_down_threshold);
printf(" scaling.idle_timeout_ms: %d\n", config->scaling.idle_timeout_ms);
printf(" streaming.read_buffer_size: %zu\n", config->streaming.read_buffer_size);
printf(" streaming.write_buffer_size: %zu\n", config->streaming.write_buffer_size);
printf(" streaming.sendfile_threshold: %zu\n", config->streaming.sendfile_threshold);
printf(" streaming.chunk_size: %zu\n", config->streaming.chunk_size);
printf(" streaming.max_memory_per_request: %zu\n", config->streaming.max_memory_per_request);
printf(" defaults.cgi_enabled: %d\n", config->defaults.default_cgi_enabled);
printf(" defaults.cgi_timeout: %d\n", config->defaults.default_cgi_timeout);
printf(" defaults.webserve_enabled: %d\n", config->defaults.default_webserve_enabled);
printf(" defaults.directory_listing: %d\n", config->defaults.default_directory_listing);
printf(" defaults.default_index: %s\n", config->defaults.default_index);
printf(" defaults.perm_read: %d\n", config->defaults.default_perm_read);
printf(" defaults.perm_write: %d\n", config->defaults.default_perm_write);
printf(" defaults.perm_execute: %d\n", config->defaults.default_perm_execute);
}
+94
View File
@@ -0,0 +1,94 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "nwedav.h"
typedef struct {
char bind_address[64];
uint16_t port;
uint16_t ssl_port;
size_t max_connections;
int connection_timeout;
int keepalive_timeout;
size_t max_request_size;
int worker_threads;
struct {
int enabled;
char cert_file[PATH_MAX];
char key_file[PATH_MAX];
char ca_file[PATH_MAX];
int verify_client;
char min_protocol[16];
char ciphers[512];
} ssl;
struct {
char root_path[PATH_MAX];
char temp_path[PATH_MAX];
char database_path[PATH_MAX];
char properties_path[PATH_MAX];
char locks_path[PATH_MAX];
} storage;
struct {
auth_method_t method;
char realm[128];
int session_timeout;
int max_failed_attempts;
int lockout_duration;
} auth;
struct {
uint64_t default_quota;
int enforce;
int warning_threshold;
} quota;
struct {
log_level_t level;
char file[PATH_MAX];
char access_log[PATH_MAX];
size_t max_size;
int rotate_count;
} logging;
struct {
int enabled;
size_t min_threads;
size_t max_threads;
size_t scale_up_threshold;
size_t scale_down_threshold;
int scale_up_step;
int scale_down_step;
int idle_timeout_ms;
int check_interval_ms;
} scaling;
struct {
size_t read_buffer_size;
size_t write_buffer_size;
size_t sendfile_threshold;
size_t chunk_size;
size_t max_memory_per_request;
} streaming;
struct {
int default_cgi_enabled;
int default_cgi_timeout;
size_t default_cgi_max_output;
int default_webserve_enabled;
int default_directory_listing;
char default_index[64];
int default_perm_read;
int default_perm_write;
int default_perm_execute;
} defaults;
} config_t;
void config_set_defaults(config_t *config);
int config_load(const char *path, config_t *config);
int config_validate(config_t *config);
void config_dump(config_t *config);
#endif
BIN
View File
Binary file not shown.
+123
View File
@@ -0,0 +1,123 @@
#include "logging.h"
#include "../platform/memory.h"
#include <string.h>
logger_t *g_logger = NULL;
struct logger {
FILE *file;
FILE *access_file;
log_level_t level;
mutex_t mutex;
char file_path[PATH_MAX];
size_t max_size;
int rotate_count;
};
static const char *level_names[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};
logger_t *logger_create(const char *file_path, log_level_t level) {
logger_t *log = calloc(1, sizeof(logger_t));
if (!log) return NULL;
log->level = level;
log->max_size = 100 * 1024 * 1024;
log->rotate_count = 10;
if (pal_mutex_init(&log->mutex) != NWEDAV_OK) {
free(log);
return NULL;
}
if (file_path && file_path[0]) {
strncpy(log->file_path, file_path, sizeof(log->file_path) - 1);
log->file = fopen(file_path, "a");
}
if (!log->file) {
log->file = stderr;
}
return log;
}
void logger_destroy(logger_t *log) {
if (!log) return;
pal_mutex_lock(&log->mutex);
if (log->file && log->file != stderr && log->file != stdout) {
fclose(log->file);
}
if (log->access_file && log->access_file != stderr && log->access_file != stdout) {
fclose(log->access_file);
}
pal_mutex_unlock(&log->mutex);
pal_mutex_destroy(&log->mutex);
free(log);
}
void logger_set_level(logger_t *log, log_level_t level) {
if (log) {
log->level = level;
}
}
void logger_log(logger_t *log, log_level_t level, const char *file, int line, const char *fmt, ...) {
if (!log || level < log->level) return;
pal_mutex_lock(&log->mutex);
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
const char *basename = file;
const char *p = strrchr(file, '/');
if (p) basename = p + 1;
#ifdef _WIN32
p = strrchr(basename, '\\');
if (p) basename = p + 1;
#endif
fprintf(log->file, "[%s] [%s] [%s:%d] ",
timestamp, level_names[level], basename, line);
va_list ap;
va_start(ap, fmt);
vfprintf(log->file, fmt, ap);
va_end(ap);
fprintf(log->file, "\n");
fflush(log->file);
pal_mutex_unlock(&log->mutex);
}
void logger_access(logger_t *log, const char *client_ip, const char *method,
const char *uri, int status, size_t bytes, const char *user_agent) {
if (!log) return;
FILE *out = log->access_file ? log->access_file : log->file;
pal_mutex_lock(&log->mutex);
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%d/%b/%Y:%H:%M:%S %z", tm);
fprintf(out, "%s - - [%s] \"%s %s HTTP/1.1\" %d %zu \"-\" \"%s\"\n",
client_ip ? client_ip : "-",
timestamp,
method ? method : "-",
uri ? uri : "-",
status,
bytes,
user_agent ? user_agent : "-");
fflush(out);
pal_mutex_unlock(&log->mutex);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef LOGGING_H
#define LOGGING_H
#include "nwedav.h"
#include "../platform/pal.h"
typedef struct logger logger_t;
logger_t *logger_create(const char *file_path, log_level_t level);
void logger_destroy(logger_t *log);
void logger_set_level(logger_t *log, log_level_t level);
void logger_log(logger_t *log, log_level_t level, const char *file, int line, const char *fmt, ...);
void logger_access(logger_t *log, const char *client_ip, const char *method,
const char *uri, int status, size_t bytes, const char *user_agent);
extern logger_t *g_logger;
#define LOG_TRACE(...) logger_log(g_logger, LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_DEBUG(...) logger_log(g_logger, LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) logger_log(g_logger, LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) logger_log(g_logger, LOG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) logger_log(g_logger, LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_FATAL(...) logger_log(g_logger, LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#endif
Binary file not shown.
+611
View File
@@ -0,0 +1,611 @@
#include "server.h"
#include "../http/parser.h"
#include "../http/response.h"
#include "../http/streaming.h"
#include "../http/webserve.h"
#include "../webdav/methods.h"
#include "../cgi/cgi.h"
#include "../storage/pathmapping.h"
#include "../user/usermgr.h"
#include "../platform/memory.h"
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sqlite3.h>
struct server {
config_t *config;
socket_t listen_fd;
event_loop_t *event_loop;
thread_pool_t *thread_pool;
connection_pool_t *conn_pool;
storage_backend_t *storage;
property_store_t *properties;
lock_manager_t *locks;
auth_context_t *auth;
path_mapping_manager_t *pathmapping;
streaming_config_t streaming;
volatile int running;
volatile size_t total_requests;
};
static server_t *g_server = NULL;
static void signal_handler(int sig) {
(void)sig;
if (g_server) {
g_server->running = 0;
}
}
server_t *server_create(config_t *config) {
server_t *server = calloc(1, sizeof(server_t));
if (!server) return NULL;
server->config = config;
server->listen_fd = SOCKET_INVALID;
server->storage = storage_create(config->storage.root_path);
if (!server->storage) {
LOG_ERROR("Failed to create storage backend at '%s'", config->storage.root_path);
free(server);
return NULL;
}
server->properties = property_store_create(config->storage.properties_path);
if (!server->properties) {
LOG_ERROR("Failed to create property store at '%s'", config->storage.properties_path);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->locks = lock_manager_create(config->storage.locks_path);
if (!server->locks) {
LOG_ERROR("Failed to create lock manager at '%s'", config->storage.locks_path);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->auth = auth_create(config->storage.database_path,
config->auth.method, config->auth.realm);
if (!server->auth) {
LOG_ERROR("Failed to create auth context");
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
sqlite3 *db = auth_get_db(server->auth);
if (db) {
server->pathmapping = pathmapping_create(db);
}
streaming_config_init(&server->streaming);
if (config->scaling.enabled) {
threadpool_config_t tp_config;
threadpool_config_init(&tp_config);
tp_config.min_threads = config->scaling.min_threads;
tp_config.max_threads = config->scaling.max_threads;
tp_config.initial_threads = (size_t)config->worker_threads;
tp_config.scale_up_threshold = config->scaling.scale_up_threshold;
tp_config.scale_down_threshold = config->scaling.scale_down_threshold;
tp_config.scale_up_step = config->scaling.scale_up_step;
tp_config.scale_down_step = config->scaling.scale_down_step;
tp_config.idle_timeout_ms = config->scaling.idle_timeout_ms;
tp_config.check_interval_ms = config->scaling.check_interval_ms;
server->thread_pool = threadpool_create_adaptive(&tp_config);
} else {
server->thread_pool = threadpool_create(config->worker_threads);
}
if (!server->thread_pool) {
LOG_ERROR("Failed to create thread pool with %d threads", config->worker_threads);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->conn_pool = connpool_create(config->max_connections);
if (!server->conn_pool) {
LOG_ERROR("Failed to create connection pool");
threadpool_destroy(server->thread_pool);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->event_loop = event_loop_create(config->max_connections);
if (!server->event_loop) {
LOG_ERROR("Failed to create event loop");
connpool_destroy(server->conn_pool);
threadpool_destroy(server->thread_pool);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
return server;
}
void server_destroy(server_t *server) {
if (!server) return;
server_stop(server);
event_loop_destroy(server->event_loop);
connpool_destroy(server->conn_pool);
threadpool_destroy(server->thread_pool);
if (server->pathmapping) {
pathmapping_destroy(server->pathmapping);
}
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
}
static int create_listen_socket(server_t *server) {
server->listen_fd = pal_socket_create(AF_INET, SOCK_STREAM, 0);
if (server->listen_fd == SOCKET_INVALID) {
LOG_ERROR("Failed to create socket");
return NWEDAV_ERROR_NETWORK;
}
pal_socket_setreuseaddr(server->listen_fd, 1);
pal_socket_setreuseport(server->listen_fd, 1);
pal_socket_setnonblock(server->listen_fd, 1);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(server->config->port);
if (strcmp(server->config->bind_address, "0.0.0.0") == 0) {
addr.sin_addr.s_addr = INADDR_ANY;
} else {
addr.sin_addr.s_addr = inet_addr(server->config->bind_address);
}
if (pal_socket_bind(server->listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
LOG_ERROR("Failed to bind to %s:%d", server->config->bind_address, server->config->port);
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
return NWEDAV_ERROR_NETWORK;
}
if (pal_socket_listen(server->listen_fd, 1024) != 0) {
LOG_ERROR("Failed to listen on socket");
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
return NWEDAV_ERROR_NETWORK;
}
return NWEDAV_OK;
}
typedef struct {
server_t *server;
connection_t *conn;
} request_task_t;
static void handle_request(void *arg) {
request_task_t *task = (request_task_t *)arg;
server_t *server = task->server;
connection_t *conn = task->conn;
free(task);
size_t consumed = 0;
int ret = http_parse_request(conn->read_buf->data, conn->read_buf->size,
&conn->request, &consumed);
if (ret != NWEDAV_OK) {
http_response_error(&conn->response, HTTP_400_BAD_REQUEST, "Invalid request");
} else {
buffer_consume(conn->read_buf, consumed);
if (conn->request.content_length > 0) {
while (conn->read_buf->size < conn->request.content_length) {
char buf[8192];
ssize_t n = pal_socket_recv(conn->fd, buf, sizeof(buf), 0);
if (n > 0) {
buffer_append(conn->read_buf, buf, n);
} else if (n == 0) {
break;
} else {
int err = pal_socket_error();
if (err == EAGAIN || err == EWOULDBLOCK) {
pal_sleep_ms(1);
continue;
}
break;
}
}
size_t body_len = conn->read_buf->size;
if (body_len > conn->request.content_length) {
body_len = conn->request.content_length;
}
if (body_len > 0) {
conn->request.body = malloc(body_len + 1);
if (conn->request.body) {
memcpy(conn->request.body, conn->read_buf->data, body_len);
conn->request.body[body_len] = '\0';
conn->request.body_len = body_len;
}
}
}
auth_user_t user;
ret = auth_check(server->auth, &conn->request, &user);
if (ret != NWEDAV_OK) {
auth_build_challenge(server->auth, &conn->response);
} else {
webdav_context_t ctx = {
.storage = server->storage,
.properties = server->properties,
.locks = server->locks,
.user_id = user.id,
.quota_bytes = user.quota_bytes,
.used_bytes = user.used_bytes,
.use_custom_directory = user.use_custom_directory,
.cgi_enabled = user.cgi_enabled,
.cgi_timeout = user.cgi_timeout,
.cgi_max_output = user.cgi_max_output,
.webserve_enabled = user.webserve_enabled,
.directory_listing = user.directory_listing,
.perm_read = user.perm_read,
.perm_write = user.perm_write,
.perm_execute = user.perm_execute,
.server_port = server->config->port,
.client_fd = conn->fd
};
strncpy(ctx.username, user.username, sizeof(ctx.username) - 1);
ctx.username[sizeof(ctx.username) - 1] = '\0';
strncpy(ctx.home_directory, user.home_directory, sizeof(ctx.home_directory) - 1);
ctx.home_directory[sizeof(ctx.home_directory) - 1] = '\0';
strncpy(ctx.custom_directory, user.custom_directory, sizeof(ctx.custom_directory) - 1);
ctx.custom_directory[sizeof(ctx.custom_directory) - 1] = '\0';
strncpy(ctx.default_index, user.default_index, sizeof(ctx.default_index) - 1);
ctx.default_index[sizeof(ctx.default_index) - 1] = '\0';
strncpy(ctx.client_ip, conn->client_ip, sizeof(ctx.client_ip) - 1);
ctx.client_ip[sizeof(ctx.client_ip) - 1] = '\0';
strncpy(ctx.server_name, server->config->bind_address, sizeof(ctx.server_name) - 1);
ctx.server_name[sizeof(ctx.server_name) - 1] = '\0';
int handled = 0;
path_mapping_t mapping;
char resolved_path[PATH_MAX];
if (server->pathmapping) {
ret = pathmapping_resolve(server->pathmapping, conn->request.uri,
&mapping, resolved_path, sizeof(resolved_path));
if (ret == NWEDAV_OK) {
if (!mapping.permission_read && !ctx.perm_read) {
http_response_error(&conn->response, HTTP_403_FORBIDDEN, "Access denied");
handled = 1;
} else if (mapping.cgi_enabled && ctx.cgi_enabled && ctx.perm_execute) {
if (cgi_is_executable(resolved_path)) {
cgi_config_t cgi_cfg;
cgi_config_init(&cgi_cfg);
cgi_cfg.enabled = 1;
cgi_cfg.timeout_seconds = ctx.cgi_timeout > 0 ? ctx.cgi_timeout : 30;
cgi_cfg.max_output_bytes = ctx.cgi_max_output > 0 ? ctx.cgi_max_output : (10 * 1024 * 1024);
strncpy(cgi_cfg.client_ip, ctx.client_ip, sizeof(cgi_cfg.client_ip) - 1);
strncpy(cgi_cfg.server_name, ctx.server_name, sizeof(cgi_cfg.server_name) - 1);
cgi_cfg.server_port = ctx.server_port;
const char *path_info = conn->request.uri + strlen(mapping.url_path);
cgi_execute(resolved_path, path_info, &conn->request, &conn->response,
conn->fd, &cgi_cfg);
handled = 1;
}
}
if (!handled && mapping.webserve_enabled && ctx.webserve_enabled) {
webserve_config_t ws_cfg;
webserve_config_init(&ws_cfg);
ws_cfg.enabled = 1;
ws_cfg.directory_listing = mapping.directory_listing && ctx.directory_listing;
ws_cfg.delete_allowed = ctx.perm_write;
if (mapping.default_index[0]) {
strncpy(ws_cfg.default_index, mapping.default_index, sizeof(ws_cfg.default_index) - 1);
} else if (ctx.default_index[0]) {
strncpy(ws_cfg.default_index, ctx.default_index, sizeof(ws_cfg.default_index) - 1);
}
webserve_handle_get(resolved_path, conn->request.uri,
&conn->request, &conn->response, &ws_cfg);
handled = 1;
}
}
}
if (!handled) {
if (conn->request.method == HTTP_METHOD_GET || conn->request.method == HTTP_METHOD_HEAD) {
if (!ctx.perm_read) {
http_response_error(&conn->response, HTTP_403_FORBIDDEN, "Read access denied");
} else {
webdav_dispatch(&ctx, &conn->request, &conn->response);
}
} else if (conn->request.method == HTTP_METHOD_PUT ||
conn->request.method == HTTP_METHOD_DELETE ||
conn->request.method == HTTP_METHOD_MKCOL ||
conn->request.method == HTTP_METHOD_COPY ||
conn->request.method == HTTP_METHOD_MOVE ||
conn->request.method == HTTP_METHOD_PROPPATCH) {
if (!ctx.perm_write) {
http_response_error(&conn->response, HTTP_403_FORBIDDEN, "Write access denied");
} else {
webdav_dispatch(&ctx, &conn->request, &conn->response);
}
} else {
webdav_dispatch(&ctx, &conn->request, &conn->response);
}
}
logger_access(g_logger, conn->client_ip,
http_method_to_string(conn->request.method),
conn->request.uri,
conn->response.status,
conn->response.body_len,
http_get_header(&conn->request, "User-Agent"));
}
}
buffer_t *response_buf = buffer_create(4096);
if (conn->response.sendfile_fd >= 0) {
http_response_build_headers_only(&conn->response, response_buf);
} else {
http_response_build(&conn->response, response_buf);
}
pal_socket_cork(conn->fd, 1);
ssize_t sent = 0;
while ((size_t)sent < response_buf->size) {
ssize_t n = pal_socket_send(conn->fd, response_buf->data + sent,
response_buf->size - sent, 0);
if (n <= 0) break;
sent += n;
}
buffer_destroy(response_buf);
if (conn->response.sendfile_fd >= 0) {
off_t offset = conn->response.sendfile_offset;
size_t remaining = conn->response.sendfile_len;
while (remaining > 0) {
ssize_t n = pal_sendfile(conn->fd, conn->response.sendfile_fd, &offset, remaining);
if (n <= 0) break;
remaining -= n;
}
close(conn->response.sendfile_fd);
conn->response.sendfile_fd = -1;
}
pal_socket_cork(conn->fd, 0);
server->total_requests++;
int keep_alive = conn->keep_alive &&
conn->request.version_major >= 1 &&
conn->request.version_minor >= 1 &&
conn->requests_served < 100;
if (keep_alive) {
pal_mutex_lock(&conn->mutex);
connection_reset(conn);
conn->requests_served++;
conn->state = CONN_STATE_READING_REQUEST;
conn->processing = 0;
pal_mutex_unlock(&conn->mutex);
event_loop_add(server->event_loop, conn->fd, EVENT_READ, conn);
} else {
pal_mutex_lock(&conn->mutex);
conn->processing = 0;
pal_mutex_unlock(&conn->mutex);
connection_close(conn);
connpool_release(server->conn_pool, conn);
}
}
static void accept_connections(server_t *server) {
while (1) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
socket_t client_fd = pal_socket_accept(server->listen_fd,
(struct sockaddr *)&addr, &addr_len);
if (client_fd == SOCKET_INVALID) {
break;
}
connection_t *conn = connpool_acquire(server->conn_pool);
if (!conn) {
LOG_WARN("Connection pool exhausted, rejecting connection");
pal_socket_close(client_fd);
continue;
}
pal_socket_setnonblock(client_fd, 1);
pal_socket_setnodelay(client_fd, 1);
connection_init(conn, client_fd, (struct sockaddr *)&addr, addr_len);
event_loop_add(server->event_loop, client_fd, EVENT_READ, conn);
LOG_DEBUG("Accepted connection from %s", conn->client_ip);
}
}
static void handle_connection_event(server_t *server, connection_t *conn, event_flags_t events) {
pal_mutex_lock(&conn->mutex);
if (conn->processing) {
pal_mutex_unlock(&conn->mutex);
return;
}
pal_mutex_unlock(&conn->mutex);
if (events & (EVENT_ERROR | EVENT_HUP)) {
event_loop_remove(server->event_loop, conn->fd);
connection_close(conn);
connpool_release(server->conn_pool, conn);
return;
}
if (events & EVENT_READ) {
char buf[8192];
ssize_t n;
while ((n = pal_socket_recv(conn->fd, buf, sizeof(buf), 0)) > 0) {
buffer_append(conn->read_buf, buf, n);
}
if (n == 0) {
event_loop_remove(server->event_loop, conn->fd);
connection_close(conn);
connpool_release(server->conn_pool, conn);
return;
}
char *end = memmem(conn->read_buf->data, conn->read_buf->size, "\r\n\r\n", 4);
if (end) {
event_loop_remove(server->event_loop, conn->fd);
pal_mutex_lock(&conn->mutex);
conn->state = CONN_STATE_PROCESSING;
conn->processing = 1;
pal_mutex_unlock(&conn->mutex);
request_task_t *task = malloc(sizeof(request_task_t));
task->server = server;
task->conn = conn;
threadpool_submit(server->thread_pool, handle_request, task);
}
}
}
int server_start(server_t *server) {
if (!server) return NWEDAV_ERROR;
int ret = create_listen_socket(server);
if (ret != NWEDAV_OK) {
return ret;
}
server->running = 1;
g_server = server;
#ifndef _WIN32
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGPIPE, SIG_IGN);
#endif
event_loop_add(server->event_loop, server->listen_fd, EVENT_READ, NULL);
LOG_INFO("Server started on %s:%d", server->config->bind_address, server->config->port);
LOG_INFO("Worker threads: %d", server->config->worker_threads);
LOG_INFO("Max connections: %zu", server->config->max_connections);
LOG_INFO("Storage root: %s", server->config->storage.root_path);
if (server->config->scaling.enabled) {
LOG_INFO("Adaptive scaling: enabled (min=%zu, max=%zu)",
server->config->scaling.min_threads, server->config->scaling.max_threads);
}
return NWEDAV_OK;
}
int server_stop(server_t *server) {
if (!server) return NWEDAV_ERROR;
server->running = 0;
if (server->listen_fd != SOCKET_INVALID) {
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
}
LOG_INFO("Server stopped");
return NWEDAV_OK;
}
int server_run(server_t *server) {
if (!server) return NWEDAV_ERROR;
event_t events[64];
while (server->running) {
int n = event_loop_wait(server->event_loop, events, 64, 1000);
if (n < 0) {
if (server->running) {
LOG_ERROR("Event loop error");
}
break;
}
for (int i = 0; i < n; i++) {
if (events[i].user_data == NULL) {
accept_connections(server);
} else {
connection_t *conn = (connection_t *)events[i].user_data;
handle_connection_event(server, conn, events[i].events);
}
}
static time_t last_cleanup = 0;
time_t now = time(NULL);
if (now - last_cleanup > 60) {
lock_manager_cleanup_expired(server->locks);
last_cleanup = now;
}
if (server->config->scaling.enabled) {
threadpool_scale_check(server->thread_pool);
}
}
return NWEDAV_OK;
}
int server_is_running(server_t *server) {
return server ? server->running : 0;
}
void server_stats(server_t *server, size_t *connections, size_t *requests) {
if (connections) {
*connections = connpool_active_count(server->conn_pool);
}
if (requests) {
*requests = server->total_requests;
}
}
void server_threadpool_stats(server_t *server, threadpool_stats_t *stats) {
if (server && server->thread_pool && stats) {
threadpool_get_stats(server->thread_pool, stats);
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef SERVER_H
#define SERVER_H
#include "nwedav.h"
#include "config.h"
#include "logging.h"
#include "../platform/pal.h"
#include "../concurrency/threadpool.h"
#include "../concurrency/eventloop.h"
#include "../concurrency/connpool.h"
#include "../storage/backend.h"
#include "../storage/properties.h"
#include "../storage/locks.h"
#include "../auth/auth.h"
typedef struct server server_t;
server_t *server_create(config_t *config);
void server_destroy(server_t *server);
int server_start(server_t *server);
int server_stop(server_t *server);
int server_run(server_t *server);
int server_is_running(server_t *server);
void server_stats(server_t *server, size_t *connections, size_t *requests);
void server_threadpool_stats(server_t *server, threadpool_stats_t *stats);
#endif
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+60
View File
@@ -10,6 +10,7 @@ static const struct {
{HTTP_200_OK, "OK"},
{HTTP_201_CREATED, "Created"},
{HTTP_204_NO_CONTENT, "No Content"},
{HTTP_206_PARTIAL_CONTENT, "Partial Content"},
{HTTP_207_MULTI_STATUS, "Multi-Status"},
{HTTP_301_MOVED_PERMANENTLY, "Moved Permanently"},
{HTTP_302_FOUND, "Found"},
@@ -23,6 +24,7 @@ static const struct {
{HTTP_412_PRECONDITION_FAILED, "Precondition Failed"},
{HTTP_413_PAYLOAD_TOO_LARGE, "Payload Too Large"},
{HTTP_415_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"},
{HTTP_416_RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"},
{HTTP_423_LOCKED, "Locked"},
{HTTP_424_FAILED_DEPENDENCY, "Failed Dependency"},
{HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error"},
@@ -252,3 +254,61 @@ int http_response_redirect(http_response_t *res, http_status_t status, const cha
return NWEDAV_OK;
}
int http_response_build_headers(http_response_t *res, char *buf, size_t buf_size) {
if (!res || !buf || buf_size == 0) return 0;
int pos = 0;
int n;
n = snprintf(buf + pos, buf_size - pos, "HTTP/1.1 %d %s\r\n",
res->status, http_status_text(res->status));
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += n;
response_header_t *h = res->headers;
int has_server = 0;
int has_date = 0;
int has_connection = 0;
while (h) {
if (str_casecmp(h->name, "Server") == 0) has_server = 1;
if (str_casecmp(h->name, "Date") == 0) has_date = 1;
if (str_casecmp(h->name, "Connection") == 0) has_connection = 1;
n = snprintf(buf + pos, buf_size - pos, "%s: %s\r\n", h->name, h->value);
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += n;
h = h->next;
}
if (!has_server) {
n = snprintf(buf + pos, buf_size - pos, "Server: nwedav/" NWEDAV_VERSION_STRING "\r\n");
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += 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);
n = snprintf(buf + pos, buf_size - pos, "Date: %s\r\n", date);
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += n;
}
if (!has_connection) {
n = snprintf(buf + pos, buf_size - pos, "Connection: %s\r\n",
res->keep_alive ? "keep-alive" : "close");
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += n;
}
n = snprintf(buf + pos, buf_size - pos, "\r\n");
if (n < 0 || (size_t)n >= buf_size - pos) return 0;
pos += n;
return pos;
}
+1
View File
@@ -32,6 +32,7 @@ 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);
int http_response_build_headers(http_response_t *res, char *buf, size_t buf_size);
const char *http_status_text(http_status_t status);
Binary file not shown.
+141
View File
@@ -0,0 +1,141 @@
#include "streaming.h"
#include "../platform/pal.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef _WIN32
#include <unistd.h>
#endif
void streaming_config_init(streaming_config_t *config) {
if (!config) return;
config->read_buffer_size = 32768;
config->write_buffer_size = 65536;
config->sendfile_threshold = 16384;
config->chunk_size = 8192;
config->max_memory_per_request = 10 * 1024 * 1024;
}
int streaming_send_all(socket_t fd, const char *data, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t n = pal_socket_send(fd, data + sent, len - sent, 0);
if (n <= 0) {
return NWEDAV_ERROR;
}
sent += n;
}
return NWEDAV_OK;
}
int streaming_send_file(socket_t fd, int file_fd, off_t offset, size_t len, size_t buffer_size) {
if (buffer_size == 0) buffer_size = STREAMING_DEFAULT_BUFFER_SIZE;
#ifndef _WIN32
{
off_t off = offset;
size_t sf_remaining = len;
ssize_t sent = pal_sendfile(fd, file_fd, &off, sf_remaining);
if (sent > 0) {
sf_remaining -= sent;
while (sf_remaining > 0) {
sent = pal_sendfile(fd, file_fd, &off, sf_remaining);
if (sent <= 0) break;
sf_remaining -= sent;
}
if (sf_remaining == 0) {
return NWEDAV_OK;
}
}
if (lseek(file_fd, offset, SEEK_SET) < 0) {
return NWEDAV_ERROR;
}
}
#endif
char *buffer = malloc(buffer_size);
if (!buffer) return NWEDAV_ERROR;
size_t remaining = len;
int result = NWEDAV_OK;
while (remaining > 0) {
size_t to_read = remaining < buffer_size ? remaining : buffer_size;
#ifdef _WIN32
DWORD bytes_read;
if (!ReadFile((HANDLE)_get_osfhandle(file_fd), buffer, (DWORD)to_read, &bytes_read, NULL) || bytes_read == 0) {
result = NWEDAV_ERROR;
break;
}
ssize_t n = bytes_read;
#else
ssize_t n = read(file_fd, buffer, to_read);
if (n <= 0) {
result = NWEDAV_ERROR;
break;
}
#endif
if (streaming_send_all(fd, buffer, n) != NWEDAV_OK) {
result = NWEDAV_ERROR;
break;
}
remaining -= n;
}
free(buffer);
return result;
}
int streaming_send_file_range(socket_t fd, int file_fd, int64_t start, int64_t end, size_t buffer_size) {
#ifndef _WIN32
if (lseek(file_fd, start, SEEK_SET) < 0) {
return NWEDAV_ERROR;
}
#else
LARGE_INTEGER li;
li.QuadPart = start;
if (!SetFilePointerEx((HANDLE)_get_osfhandle(file_fd), li, NULL, FILE_BEGIN)) {
return NWEDAV_ERROR;
}
#endif
size_t len = (size_t)(end - start + 1);
return streaming_send_file(fd, file_fd, start, len, buffer_size);
}
int streaming_send_chunked_start(socket_t fd) {
(void)fd;
return NWEDAV_OK;
}
int streaming_send_chunk(socket_t fd, const char *data, size_t len) {
if (len == 0) return NWEDAV_OK;
char header[32];
int header_len = snprintf(header, sizeof(header), "%zx\r\n", len);
if (header_len <= 0) return NWEDAV_ERROR;
if (streaming_send_all(fd, header, header_len) != NWEDAV_OK) {
return NWEDAV_ERROR;
}
if (streaming_send_all(fd, data, len) != NWEDAV_OK) {
return NWEDAV_ERROR;
}
if (streaming_send_all(fd, "\r\n", 2) != NWEDAV_OK) {
return NWEDAV_ERROR;
}
return NWEDAV_OK;
}
int streaming_send_chunked_end(socket_t fd) {
return streaming_send_all(fd, "0\r\n\r\n", 5);
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef STREAMING_H
#define STREAMING_H
#include "nwedav.h"
#define STREAMING_DEFAULT_BUFFER_SIZE 32768
#define STREAMING_DEFAULT_CHUNK_SIZE 8192
typedef struct {
size_t read_buffer_size;
size_t write_buffer_size;
size_t sendfile_threshold;
size_t chunk_size;
size_t max_memory_per_request;
} streaming_config_t;
void streaming_config_init(streaming_config_t *config);
int streaming_send_file(socket_t fd, int file_fd, off_t offset, size_t len, size_t buffer_size);
int streaming_send_file_range(socket_t fd, int file_fd, int64_t start, int64_t end, size_t buffer_size);
int streaming_send_chunked_start(socket_t fd);
int streaming_send_chunk(socket_t fd, const char *data, size_t len);
int streaming_send_chunked_end(socket_t fd);
int streaming_send_all(socket_t fd, const char *data, size_t len);
#endif
+344
View File
@@ -0,0 +1,344 @@
#include "webserve.h"
#include "headers.h"
#include "../platform/file.h"
#include "../platform/time.h"
#include <string.h>
#include <stdio.h>
void webserve_config_init(webserve_config_t *config) {
if (!config) return;
config->enabled = 1;
config->directory_listing = 1;
strncpy(config->default_index, "index.html", sizeof(config->default_index) - 1);
config->default_index[sizeof(config->default_index) - 1] = '\0';
config->delete_allowed = 0;
}
static void html_escape(const char *src, char *dst, size_t dst_size) {
size_t di = 0;
while (*src && di < dst_size - 6) {
switch (*src) {
case '<':
dst[di++] = '&';
dst[di++] = 'l';
dst[di++] = 't';
dst[di++] = ';';
break;
case '>':
dst[di++] = '&';
dst[di++] = 'g';
dst[di++] = 't';
dst[di++] = ';';
break;
case '&':
dst[di++] = '&';
dst[di++] = 'a';
dst[di++] = 'm';
dst[di++] = 'p';
dst[di++] = ';';
break;
case '"':
dst[di++] = '&';
dst[di++] = 'q';
dst[di++] = 'u';
dst[di++] = 'o';
dst[di++] = 't';
dst[di++] = ';';
break;
default:
dst[di++] = *src;
}
src++;
}
dst[di] = '\0';
}
static void format_size(int64_t size, char *buf, size_t buf_size) {
if (size < 1024) {
snprintf(buf, buf_size, "%lld B", (long long)size);
} else if (size < 1024 * 1024) {
snprintf(buf, buf_size, "%.1f KB", size / 1024.0);
} else if (size < 1024 * 1024 * 1024) {
snprintf(buf, buf_size, "%.1f MB", size / (1024.0 * 1024.0));
} else {
snprintf(buf, buf_size, "%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
}
}
int webserve_generate_directory_listing(const char *fs_path, const char *uri,
buffer_t *out, int delete_allowed) {
if (!fs_path || !uri || !out) return NWEDAV_ERROR;
dir_handle_t *dh = dir_open(fs_path);
if (!dh) return NWEDAV_ERROR;
char escaped_uri[PATH_MAX * 2];
html_escape(uri, escaped_uri, sizeof(escaped_uri));
buffer_append_str(out, "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n");
buffer_append_str(out, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n");
buffer_append_str(out, "<title>Index of ");
buffer_append_str(out, escaped_uri);
buffer_append_str(out, "</title>\n<style>\n");
buffer_append_str(out, "body{font-family:sans-serif;margin:20px;background:#f5f5f5}\n");
buffer_append_str(out, "h1{color:#333;border-bottom:1px solid #ddd;padding-bottom:10px}\n");
buffer_append_str(out, "table{width:100%;border-collapse:collapse;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,0.1)}\n");
buffer_append_str(out, "th,td{padding:12px;text-align:left;border-bottom:1px solid #eee}\n");
buffer_append_str(out, "th{background:#f8f8f8;font-weight:600}\n");
buffer_append_str(out, "tr:hover{background:#f9f9f9}\n");
buffer_append_str(out, "a{color:#0066cc;text-decoration:none}\n");
buffer_append_str(out, "a:hover{text-decoration:underline}\n");
buffer_append_str(out, ".dir{font-weight:500}\n");
buffer_append_str(out, ".size{color:#666;text-align:right}\n");
buffer_append_str(out, ".date{color:#888}\n");
buffer_append_str(out, ".del{color:#c00;cursor:pointer}\n");
buffer_append_str(out, "</style>\n</head>\n<body>\n");
buffer_append_str(out, "<h1>Index of ");
buffer_append_str(out, escaped_uri);
buffer_append_str(out, "</h1>\n");
buffer_append_str(out, "<table>\n<thead>\n<tr>\n");
buffer_append_str(out, "<th>Name</th><th class=\"size\">Size</th><th class=\"date\">Modified</th>");
if (delete_allowed) {
buffer_append_str(out, "<th>Action</th>");
}
buffer_append_str(out, "\n</tr>\n</thead>\n<tbody>\n");
if (strcmp(uri, "/") != 0) {
buffer_append_str(out, "<tr><td class=\"dir\"><a href=\"../\">../</a></td>");
buffer_append_str(out, "<td class=\"size\">-</td><td class=\"date\">-</td>");
if (delete_allowed) {
buffer_append_str(out, "<td>-</td>");
}
buffer_append_str(out, "</tr>\n");
}
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];
snprintf(full_path, sizeof(full_path), "%s/%s", fs_path, entry.name);
file_stat_t st;
if (file_stat(full_path, &st) != NWEDAV_OK) {
continue;
}
char escaped_name[512];
html_escape(entry.name, escaped_name, sizeof(escaped_name));
char href[PATH_MAX];
if (strcmp(uri, "/") == 0) {
snprintf(href, sizeof(href), "/%s%s", entry.name, st.is_dir ? "/" : "");
} else {
snprintf(href, sizeof(href), "%s%s%s%s",
uri,
(uri[strlen(uri) - 1] == '/') ? "" : "/",
entry.name,
st.is_dir ? "/" : "");
}
char size_str[32];
if (st.is_dir) {
strncpy(size_str, "-", sizeof(size_str));
} else {
format_size(st.size, size_str, sizeof(size_str));
}
char date_str[64];
time_format_iso8601(st.mtime, date_str, sizeof(date_str));
buffer_append_str(out, "<tr><td");
if (st.is_dir) {
buffer_append_str(out, " class=\"dir\"");
}
buffer_append_str(out, "><a href=\"");
buffer_append_str(out, href);
buffer_append_str(out, "\">");
buffer_append_str(out, escaped_name);
if (st.is_dir) {
buffer_append_str(out, "/");
}
buffer_append_str(out, "</a></td>");
buffer_append_str(out, "<td class=\"size\">");
buffer_append_str(out, size_str);
buffer_append_str(out, "</td>");
buffer_append_str(out, "<td class=\"date\">");
buffer_append_str(out, date_str);
buffer_append_str(out, "</td>");
if (delete_allowed) {
buffer_append_str(out, "<td><a class=\"del\" href=\"");
buffer_append_str(out, href);
buffer_append_str(out, "?action=delete\" onclick=\"return confirm('Delete ");
buffer_append_str(out, escaped_name);
buffer_append_str(out, "?');\">Delete</a></td>");
}
buffer_append_str(out, "</tr>\n");
}
dir_close(dh);
buffer_append_str(out, "</tbody>\n</table>\n");
buffer_append_str(out, "<p style=\"color:#888;font-size:12px;margin-top:20px\">nwedav</p>\n");
buffer_append_str(out, "</body>\n</html>\n");
return NWEDAV_OK;
}
int webserve_serve_file(const char *fs_path, http_request_t *req,
http_response_t *res, int *out_fd,
int64_t *out_offset, int64_t *out_len) {
if (!fs_path || !req || !res) return NWEDAV_ERROR;
file_stat_t st;
if (file_stat(fs_path, &st) != NWEDAV_OK) {
http_response_error(res, HTTP_404_NOT_FOUND, "File not found");
return NWEDAV_ERROR_NOT_FOUND;
}
if (st.is_dir) {
http_response_error(res, HTTP_403_FORBIDDEN, "Cannot serve directory as file");
return NWEDAV_ERROR_FORBIDDEN;
}
const char *range_header = http_get_header(req, "Range");
int64_t start = 0;
int64_t end = st.size - 1;
int is_range = 0;
if (range_header && st.size > 0) {
if (http_parse_range(range_header, st.size, &start, &end) == NWEDAV_OK) {
is_range = 1;
}
}
const char *if_modified = http_get_header(req, "If-Modified-Since");
if (if_modified && !http_check_if_modified_since(if_modified, st.mtime)) {
http_response_set_status(res, HTTP_304_NOT_MODIFIED);
return NWEDAV_OK;
}
file_handle_t fh = file_open(fs_path, FILE_READ);
#ifdef _WIN32
if (fh.handle == INVALID_HANDLE_VALUE) {
#else
if (fh.fd < 0) {
#endif
http_response_error(res, HTTP_500_INTERNAL_ERROR, "Cannot open file");
return NWEDAV_ERROR;
}
const char *mime = http_get_mime_type_for_file(fs_path);
http_response_add_header(res, "Content-Type", mime);
char etag[64];
snprintf(etag, sizeof(etag), "\"%llx-%llx\"",
(unsigned long long)st.mtime, (unsigned long long)st.size);
http_response_add_header(res, "ETag", etag);
char date_buf[64];
time_format_http(st.mtime, date_buf, sizeof(date_buf));
http_response_add_header(res, "Last-Modified", date_buf);
http_response_add_header(res, "Accept-Ranges", "bytes");
if (is_range) {
http_response_set_status(res, HTTP_206_PARTIAL_CONTENT);
char range_str[128];
snprintf(range_str, sizeof(range_str), "bytes %lld-%lld/%lld",
(long long)start, (long long)end, (long long)st.size);
http_response_add_header(res, "Content-Range", range_str);
char len_str[32];
snprintf(len_str, sizeof(len_str), "%lld", (long long)(end - start + 1));
http_response_add_header(res, "Content-Length", len_str);
} else {
http_response_set_status(res, HTTP_200_OK);
char len_str[32];
snprintf(len_str, sizeof(len_str), "%lld", (long long)st.size);
http_response_add_header(res, "Content-Length", len_str);
}
if (out_fd) {
#ifdef _WIN32
*out_fd = _open_osfhandle((intptr_t)fh.handle, 0);
#else
*out_fd = fh.fd;
#endif
}
if (out_offset) *out_offset = start;
if (out_len) *out_len = end - start + 1;
return NWEDAV_OK;
}
int webserve_handle_get(const char *fs_path, const char *uri,
http_request_t *req, http_response_t *res,
webserve_config_t *config) {
if (!fs_path || !uri || !req || !res || !config) return NWEDAV_ERROR;
if (!config->enabled) {
http_response_error(res, HTTP_403_FORBIDDEN, "Web serving disabled");
return NWEDAV_ERROR_FORBIDDEN;
}
file_stat_t st;
if (file_stat(fs_path, &st) != NWEDAV_OK) {
http_response_error(res, HTTP_404_NOT_FOUND, "Not found");
return NWEDAV_ERROR_NOT_FOUND;
}
if (st.is_dir) {
if (config->default_index[0]) {
char index_path[PATH_MAX];
snprintf(index_path, sizeof(index_path), "%s/%s", fs_path, config->default_index);
if (file_exists(index_path)) {
return webserve_serve_file(index_path, req, res, NULL, NULL, NULL);
}
}
if (!config->directory_listing) {
http_response_error(res, HTTP_403_FORBIDDEN, "Directory listing disabled");
return NWEDAV_ERROR_FORBIDDEN;
}
buffer_t *listing = buffer_create(4096);
if (!listing) {
http_response_error(res, HTTP_500_INTERNAL_ERROR, "Memory allocation failed");
return NWEDAV_ERROR;
}
if (webserve_generate_directory_listing(fs_path, uri, listing, config->delete_allowed) != NWEDAV_OK) {
buffer_destroy(listing);
http_response_error(res, HTTP_500_INTERNAL_ERROR, "Cannot generate listing");
return NWEDAV_ERROR;
}
http_response_set_status(res, HTTP_200_OK);
http_response_add_header(res, "Content-Type", "text/html; charset=utf-8");
size_t body_size = listing->size;
char len_str[32];
snprintf(len_str, sizeof(len_str), "%zu", body_size);
http_response_add_header(res, "Content-Length", len_str);
res->body = buffer_detach(listing);
res->body_len = body_size;
buffer_destroy(listing);
return NWEDAV_OK;
}
return webserve_serve_file(fs_path, req, res, NULL, NULL, NULL);
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef WEBSERVE_H
#define WEBSERVE_H
#include "nwedav.h"
#include "parser.h"
#include "response.h"
#include "../platform/memory.h"
typedef struct {
int enabled;
int directory_listing;
char default_index[64];
int delete_allowed;
} webserve_config_t;
void webserve_config_init(webserve_config_t *config);
int webserve_handle_get(const char *fs_path, const char *uri,
http_request_t *req, http_response_t *res,
webserve_config_t *config);
int webserve_generate_directory_listing(const char *fs_path, const char *uri,
buffer_t *out, int delete_allowed);
int webserve_serve_file(const char *fs_path, http_request_t *req,
http_response_t *res, int *out_fd,
int64_t *out_offset, int64_t *out_len);
#endif
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+430
View File
@@ -0,0 +1,430 @@
#include "pathmapping.h"
#include "../platform/pal.h"
#include "../platform/file.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
struct path_mapping_manager {
sqlite3 *db;
mutex_t mutex;
};
path_mapping_manager_t *pathmapping_create(sqlite3 *db) {
if (!db) return NULL;
path_mapping_manager_t *mgr = calloc(1, sizeof(path_mapping_manager_t));
if (!mgr) return NULL;
if (pal_mutex_init(&mgr->mutex) != NWEDAV_OK) {
free(mgr);
return NULL;
}
mgr->db = db;
return mgr;
}
void pathmapping_destroy(path_mapping_manager_t *mgr) {
if (!mgr) return;
pal_mutex_destroy(&mgr->mutex);
free(mgr);
}
static void fill_mapping_from_row(sqlite3_stmt *stmt, path_mapping_t *mapping) {
memset(mapping, 0, sizeof(path_mapping_t));
mapping->id = sqlite3_column_int(stmt, 0);
const char *val;
val = (const char *)sqlite3_column_text(stmt, 1);
if (val) {
strncpy(mapping->url_path, val, sizeof(mapping->url_path) - 1);
}
val = (const char *)sqlite3_column_text(stmt, 2);
if (val) {
strncpy(mapping->filesystem_path, val, sizeof(mapping->filesystem_path) - 1);
}
mapping->permission_read = sqlite3_column_int(stmt, 3);
mapping->permission_execute = sqlite3_column_int(stmt, 4);
mapping->webserve_enabled = sqlite3_column_int(stmt, 5);
mapping->cgi_enabled = sqlite3_column_int(stmt, 6);
mapping->directory_listing = sqlite3_column_int(stmt, 7);
val = (const char *)sqlite3_column_text(stmt, 8);
if (val) {
strncpy(mapping->default_index, val, sizeof(mapping->default_index) - 1);
} else {
strncpy(mapping->default_index, "index.html", sizeof(mapping->default_index) - 1);
}
mapping->created_at = sqlite3_column_int64(stmt, 9);
}
int pathmapping_add(path_mapping_manager_t *mgr, path_mapping_t *mapping) {
if (!mgr || !mapping) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"INSERT INTO path_mappings (url_path, filesystem_path, permission_read, permission_execute, "
"webserve_enabled, cgi_enabled, directory_listing, default_index, created_at) "
"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, mapping->url_path, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, mapping->filesystem_path, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 3, mapping->permission_read);
sqlite3_bind_int(stmt, 4, mapping->permission_execute);
sqlite3_bind_int(stmt, 5, mapping->webserve_enabled);
sqlite3_bind_int(stmt, 6, mapping->cgi_enabled);
sqlite3_bind_int(stmt, 7, mapping->directory_listing);
sqlite3_bind_text(stmt, 8, mapping->default_index[0] ? mapping->default_index : "index.html", -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 9, time(NULL));
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int pathmapping_update(path_mapping_manager_t *mgr, path_mapping_t *mapping) {
if (!mgr || !mapping) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"UPDATE path_mappings SET filesystem_path = ?, permission_read = ?, permission_execute = ?, "
"webserve_enabled = ?, cgi_enabled = ?, directory_listing = ?, default_index = ? "
"WHERE url_path = ?";
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, mapping->filesystem_path, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, mapping->permission_read);
sqlite3_bind_int(stmt, 3, mapping->permission_execute);
sqlite3_bind_int(stmt, 4, mapping->webserve_enabled);
sqlite3_bind_int(stmt, 5, mapping->cgi_enabled);
sqlite3_bind_int(stmt, 6, mapping->directory_listing);
sqlite3_bind_text(stmt, 7, mapping->default_index, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 8, mapping->url_path, -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 pathmapping_delete(path_mapping_manager_t *mgr, const char *url_path) {
if (!mgr || !url_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM path_mappings WHERE url_path = ?";
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, url_path, -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 pathmapping_get(path_mapping_manager_t *mgr, const char *url_path, path_mapping_t *mapping) {
if (!mgr || !url_path || !mapping) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, url_path, filesystem_path, permission_read, permission_execute, "
"webserve_enabled, cgi_enabled, directory_listing, default_index, created_at "
"FROM path_mappings WHERE url_path = ?";
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, url_path, -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) {
fill_mapping_from_row(stmt, mapping);
ret = NWEDAV_OK;
} else {
ret = NWEDAV_ERROR_NOT_FOUND;
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return ret;
}
int pathmapping_resolve(path_mapping_manager_t *mgr, const char *uri,
path_mapping_t *mapping, char *resolved_path, size_t size) {
if (!mgr || !uri || !mapping || !resolved_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT id, url_path, filesystem_path, permission_read, permission_execute, "
"webserve_enabled, cgi_enabled, directory_listing, default_index, created_at "
"FROM path_mappings ORDER BY LENGTH(url_path) DESC";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_ERROR;
}
int found = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *url_prefix = (const char *)sqlite3_column_text(stmt, 1);
size_t prefix_len = strlen(url_prefix);
if (strncmp(uri, url_prefix, prefix_len) == 0) {
if (uri[prefix_len] == '\0' || uri[prefix_len] == '/' || url_prefix[prefix_len - 1] == '/') {
fill_mapping_from_row(stmt, mapping);
const char *remainder = uri + prefix_len;
while (*remainder == '/') remainder++;
if (*remainder == '\0') {
snprintf(resolved_path, size, "%s", mapping->filesystem_path);
} else {
snprintf(resolved_path, size, "%s/%s", mapping->filesystem_path, remainder);
}
char normalized[PATH_MAX];
path_normalize(resolved_path, normalized, sizeof(normalized));
strncpy(resolved_path, normalized, size - 1);
resolved_path[size - 1] = '\0';
if (!path_is_safe(resolved_path, mapping->filesystem_path)) {
found = 0;
break;
}
found = 1;
break;
}
}
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return found ? NWEDAV_OK : NWEDAV_ERROR_NOT_FOUND;
}
int pathmapping_list(path_mapping_manager_t *mgr, pathmapping_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, url_path, filesystem_path, permission_read, permission_execute, "
"webserve_enabled, cgi_enabled, directory_listing, default_index, created_at "
"FROM path_mappings ORDER BY url_path";
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) {
path_mapping_t mapping;
fill_mapping_from_row(stmt, &mapping);
if (callback(&mapping, user_data) != 0) {
break;
}
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_OK;
}
int pathmapping_count(path_mapping_manager_t *mgr) {
if (!mgr) return 0;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "SELECT COUNT(*) FROM path_mappings";
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 pathmapping_set_permission(path_mapping_manager_t *mgr, const char *url_path,
int read, int execute) {
if (!mgr || !url_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE path_mappings SET permission_read = ?, permission_execute = ? WHERE url_path = ?";
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, read);
sqlite3_bind_int(stmt, 2, execute);
sqlite3_bind_text(stmt, 3, url_path, -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 pathmapping_set_webserve(path_mapping_manager_t *mgr, const char *url_path, int enabled) {
if (!mgr || !url_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE path_mappings SET webserve_enabled = ? WHERE url_path = ?";
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);
sqlite3_bind_text(stmt, 2, url_path, -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 pathmapping_set_cgi(path_mapping_manager_t *mgr, const char *url_path, int enabled) {
if (!mgr || !url_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE path_mappings SET cgi_enabled = ? WHERE url_path = ?";
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);
sqlite3_bind_text(stmt, 2, url_path, -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 pathmapping_set_listing(path_mapping_manager_t *mgr, const char *url_path, int enabled) {
if (!mgr || !url_path) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE path_mappings SET directory_listing = ? WHERE url_path = ?";
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);
sqlite3_bind_text(stmt, 2, url_path, -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 pathmapping_set_index(path_mapping_manager_t *mgr, const char *url_path, const char *index) {
if (!mgr || !url_path || !index) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE path_mappings SET default_index = ? WHERE url_path = ?";
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, index, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, url_path, -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;
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef PATHMAPPING_H
#define PATHMAPPING_H
#include "nwedav.h"
#include <sqlite3.h>
typedef struct {
uint32_t id;
char url_path[PATH_MAX];
char filesystem_path[PATH_MAX];
int permission_read;
int permission_execute;
int webserve_enabled;
int cgi_enabled;
int directory_listing;
char default_index[64];
int64_t created_at;
} path_mapping_t;
typedef struct path_mapping_manager path_mapping_manager_t;
path_mapping_manager_t *pathmapping_create(sqlite3 *db);
void pathmapping_destroy(path_mapping_manager_t *mgr);
int pathmapping_add(path_mapping_manager_t *mgr, path_mapping_t *mapping);
int pathmapping_update(path_mapping_manager_t *mgr, path_mapping_t *mapping);
int pathmapping_delete(path_mapping_manager_t *mgr, const char *url_path);
int pathmapping_get(path_mapping_manager_t *mgr, const char *url_path, path_mapping_t *mapping);
int pathmapping_resolve(path_mapping_manager_t *mgr, const char *uri,
path_mapping_t *mapping, char *resolved_path, size_t size);
typedef int (*pathmapping_callback_t)(path_mapping_t *mapping, void *user_data);
int pathmapping_list(path_mapping_manager_t *mgr, pathmapping_callback_t callback, void *user_data);
int pathmapping_count(path_mapping_manager_t *mgr);
int pathmapping_set_permission(path_mapping_manager_t *mgr, const char *url_path,
int read, int execute);
int pathmapping_set_webserve(path_mapping_manager_t *mgr, const char *url_path, int enabled);
int pathmapping_set_cgi(path_mapping_manager_t *mgr, const char *url_path, int enabled);
int pathmapping_set_listing(path_mapping_manager_t *mgr, const char *url_path, int enabled);
int pathmapping_set_index(path_mapping_manager_t *mgr, const char *url_path, const char *index);
#endif
Binary file not shown.
Binary file not shown.
+542 -10
View File
@@ -9,8 +9,138 @@ struct user_manager {
mutex_t mutex;
};
static int get_schema_version(sqlite3 *db) {
sqlite3_stmt *stmt;
const char *sql = "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1";
int ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
return 0;
}
int version = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
version = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return version;
}
static int set_schema_version(sqlite3 *db, int version) {
sqlite3_stmt *stmt;
const char *sql = "INSERT INTO schema_version (version, applied_at) VALUES (?, ?)";
int ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) return NWEDAV_ERROR;
sqlite3_bind_int(stmt, 1, version);
sqlite3_bind_int64(stmt, 2, time(NULL));
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
static int column_exists(sqlite3 *db, const char *table, const char *column) {
char sql[256];
snprintf(sql, sizeof(sql), "PRAGMA table_info(%s)", table);
sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) return 0;
int exists = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *name = (const char *)sqlite3_column_text(stmt, 1);
if (name && strcmp(name, column) == 0) {
exists = 1;
break;
}
}
sqlite3_finalize(stmt);
return exists;
}
static int migrate_to_v2(sqlite3 *db) {
char *err = NULL;
if (!column_exists(db, "users", "custom_directory")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN custom_directory TEXT DEFAULT NULL", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "use_custom_directory")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN use_custom_directory INTEGER DEFAULT 0", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "cgi_enabled")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN cgi_enabled INTEGER DEFAULT 0", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "cgi_timeout")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN cgi_timeout INTEGER DEFAULT 30", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "cgi_max_output")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN cgi_max_output INTEGER DEFAULT 10485760", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "webserve_enabled")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN webserve_enabled INTEGER DEFAULT 1", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "directory_listing")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN directory_listing INTEGER DEFAULT 1", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "default_index")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN default_index TEXT DEFAULT 'index.html'", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "perm_read")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN perm_read INTEGER DEFAULT 1", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "perm_write")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN perm_write INTEGER DEFAULT 1", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
if (!column_exists(db, "users", "perm_execute")) {
if (sqlite3_exec(db, "ALTER TABLE users ADD COLUMN perm_execute INTEGER DEFAULT 0", NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
}
const char *mappings_sql =
"CREATE TABLE IF NOT EXISTS path_mappings ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" url_path TEXT UNIQUE NOT NULL,"
" filesystem_path TEXT NOT NULL,"
" permission_read INTEGER DEFAULT 1,"
" permission_execute INTEGER DEFAULT 0,"
" webserve_enabled INTEGER DEFAULT 1,"
" cgi_enabled INTEGER DEFAULT 0,"
" directory_listing INTEGER DEFAULT 1,"
" default_index TEXT DEFAULT 'index.html',"
" created_at INTEGER NOT NULL"
");"
"CREATE INDEX IF NOT EXISTS idx_path_mappings_url ON path_mappings(url_path);";
if (sqlite3_exec(db, mappings_sql, NULL, NULL, &err) != SQLITE_OK) {
sqlite3_free(err);
}
return NWEDAV_OK;
}
static int init_database(sqlite3 *db) {
const char *sql =
"CREATE TABLE IF NOT EXISTS schema_version ("
" version INTEGER PRIMARY KEY,"
" applied_at INTEGER NOT NULL"
");"
"CREATE TABLE IF NOT EXISTS users ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" username TEXT UNIQUE NOT NULL,"
@@ -23,7 +153,18 @@ static int init_database(sqlite3 *db) {
" enabled INTEGER DEFAULT 1,"
" created_at INTEGER NOT NULL,"
" updated_at INTEGER NOT NULL,"
" last_login INTEGER"
" last_login INTEGER,"
" custom_directory TEXT DEFAULT NULL,"
" use_custom_directory INTEGER DEFAULT 0,"
" cgi_enabled INTEGER DEFAULT 0,"
" cgi_timeout INTEGER DEFAULT 30,"
" cgi_max_output INTEGER DEFAULT 10485760,"
" webserve_enabled INTEGER DEFAULT 1,"
" directory_listing INTEGER DEFAULT 1,"
" default_index TEXT DEFAULT 'index.html',"
" perm_read INTEGER DEFAULT 1,"
" perm_write INTEGER DEFAULT 1,"
" perm_execute INTEGER DEFAULT 0"
");"
"CREATE TABLE IF NOT EXISTS groups ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
@@ -37,7 +178,20 @@ static int init_database(sqlite3 *db) {
" 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);";
"CREATE TABLE IF NOT EXISTS path_mappings ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" url_path TEXT UNIQUE NOT NULL,"
" filesystem_path TEXT NOT NULL,"
" permission_read INTEGER DEFAULT 1,"
" permission_execute INTEGER DEFAULT 0,"
" webserve_enabled INTEGER DEFAULT 1,"
" cgi_enabled INTEGER DEFAULT 0,"
" directory_listing INTEGER DEFAULT 1,"
" default_index TEXT DEFAULT 'index.html',"
" created_at INTEGER NOT NULL"
");"
"CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);"
"CREATE INDEX IF NOT EXISTS idx_path_mappings_url ON path_mappings(url_path);";
char *err = NULL;
int ret = sqlite3_exec(db, sql, NULL, NULL, &err);
@@ -46,6 +200,18 @@ static int init_database(sqlite3 *db) {
return NWEDAV_ERROR;
}
int current_version = get_schema_version(db);
if (current_version < 1) {
set_schema_version(db, 1);
current_version = 1;
}
if (current_version < 2) {
migrate_to_v2(db);
set_schema_version(db, 2);
}
return NWEDAV_OK;
}
@@ -88,9 +254,13 @@ void user_manager_destroy(user_manager_t *mgr) {
free(mgr);
}
sqlite3 *user_manager_get_db(user_manager_t *mgr) {
return mgr ? mgr->db : NULL;
}
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;
if (!mgr || !username || !password || username[0] == '\0') return NWEDAV_ERROR;
char hash[256];
if (password_hash(password, hash, sizeof(hash)) != NWEDAV_OK) {
@@ -105,8 +275,8 @@ int user_manager_add(user_manager_t *mgr, const char *username, const char *pass
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, ?, ?)";
"quota_bytes, used_bytes, enabled, created_at, updated_at, perm_read, perm_write) "
"VALUES (?, ?, ?, ?, ?, ?, 0, 1, ?, ?, 1, 1)";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
@@ -275,6 +445,32 @@ static void fill_user_from_row(sqlite3_stmt *stmt, user_t *user) {
user->created_at = sqlite3_column_int64(stmt, 8);
user->updated_at = sqlite3_column_int64(stmt, 9);
user->last_login = sqlite3_column_int64(stmt, 10);
val = (const char *)sqlite3_column_text(stmt, 11);
if (val) {
strncpy(user->custom_directory, val, sizeof(user->custom_directory) - 1);
user->custom_directory[sizeof(user->custom_directory) - 1] = '\0';
}
user->use_custom_directory = sqlite3_column_int(stmt, 12);
user->cgi_enabled = sqlite3_column_int(stmt, 13);
user->cgi_timeout = sqlite3_column_int(stmt, 14);
user->cgi_max_output = sqlite3_column_int64(stmt, 15);
user->webserve_enabled = sqlite3_column_int(stmt, 16);
user->directory_listing = sqlite3_column_int(stmt, 17);
val = (const char *)sqlite3_column_text(stmt, 18);
if (val) {
strncpy(user->default_index, val, sizeof(user->default_index) - 1);
user->default_index[sizeof(user->default_index) - 1] = '\0';
} else {
strncpy(user->default_index, "index.html", sizeof(user->default_index) - 1);
}
user->perm_read = sqlite3_column_int(stmt, 19);
user->perm_write = sqlite3_column_int(stmt, 20);
user->perm_execute = sqlite3_column_int(stmt, 21);
}
int user_manager_get_by_id(user_manager_t *mgr, uint32_t id, user_t *user) {
@@ -285,7 +481,9 @@ int user_manager_get_by_id(user_manager_t *mgr, uint32_t id, user_t *user) {
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 "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login, "
"custom_directory, use_custom_directory, cgi_enabled, cgi_timeout, cgi_max_output, "
"webserve_enabled, directory_listing, default_index, perm_read, perm_write, perm_execute "
"FROM users WHERE id = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
@@ -318,7 +516,9 @@ int user_manager_get_by_username(user_manager_t *mgr, const char *username, user
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 "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login, "
"custom_directory, use_custom_directory, cgi_enabled, cgi_timeout, cgi_max_output, "
"webserve_enabled, directory_listing, default_index, perm_read, perm_write, perm_execute "
"FROM users WHERE username = ?";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
@@ -352,7 +552,9 @@ int user_manager_authenticate(user_manager_t *mgr, const char *username,
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 "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login, "
"custom_directory, use_custom_directory, cgi_enabled, cgi_timeout, cgi_max_output, "
"webserve_enabled, directory_listing, default_index, perm_read, perm_write, perm_execute, password_hash "
"FROM users WHERE username = ? AND enabled = 1";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
@@ -372,7 +574,7 @@ int user_manager_authenticate(user_manager_t *mgr, const char *username,
fill_user_from_row(stmt, user);
const char *stored_hash = (const char *)sqlite3_column_text(stmt, 11);
const char *stored_hash = (const char *)sqlite3_column_text(stmt, 22);
int verified = password_verify(password, stored_hash);
sqlite3_finalize(stmt);
@@ -403,7 +605,9 @@ int user_manager_list(user_manager_t *mgr, user_callback_t callback, void *user_
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 "
"quota_bytes, used_bytes, enabled, created_at, updated_at, last_login, "
"custom_directory, use_custom_directory, cgi_enabled, cgi_timeout, cgi_max_output, "
"webserve_enabled, directory_listing, default_index, perm_read, perm_write, perm_execute "
"FROM users ORDER BY username";
int ret = sqlite3_prepare_v2(mgr->db, sql, -1, &stmt, NULL);
@@ -628,3 +832,331 @@ int user_manager_group_remove_user(user_manager_t *mgr, const char *group, const
return (ret == SQLITE_DONE) ? NWEDAV_OK : NWEDAV_ERROR;
}
int user_manager_group_list(user_manager_t *mgr, group_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, name, description FROM groups ORDER BY name";
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) {
group_t group;
memset(&group, 0, sizeof(group));
group.id = sqlite3_column_int(stmt, 0);
const char *val = (const char *)sqlite3_column_text(stmt, 1);
if (val) {
strncpy(group.name, val, sizeof(group.name) - 1);
}
val = (const char *)sqlite3_column_text(stmt, 2);
if (val) {
strncpy(group.description, val, sizeof(group.description) - 1);
}
if (callback(&group, user_data) != 0) {
break;
}
}
sqlite3_finalize(stmt);
pal_mutex_unlock(&mgr->mutex);
return NWEDAV_OK;
}
int user_manager_group_members(user_manager_t *mgr, const char *group_name,
user_callback_t callback, void *user_data) {
if (!mgr || !group_name || !callback) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql =
"SELECT u.id, u.username, u.email, u.display_name, u.home_directory, "
"u.quota_bytes, u.used_bytes, u.enabled, u.created_at, u.updated_at, u.last_login, "
"u.custom_directory, u.use_custom_directory, u.cgi_enabled, u.cgi_timeout, u.cgi_max_output, "
"u.webserve_enabled, u.directory_listing, u.default_index, u.perm_read, u.perm_write, u.perm_execute "
"FROM users u "
"JOIN user_groups ug ON u.id = ug.user_id "
"JOIN groups g ON ug.group_id = g.id "
"WHERE g.name = ? ORDER BY u.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, group_name, -1, SQLITE_STATIC);
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_set_custom_directory(user_manager_t *mgr, const char *username,
const char *directory) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET custom_directory = ?, use_custom_directory = 1, 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, directory, -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_clear_custom_directory(user_manager_t *mgr, const char *username) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET custom_directory = NULL, use_custom_directory = 0, 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_int64(stmt, 1, time(NULL));
sqlite3_bind_text(stmt, 2, 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_cgi_enabled(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 cgi_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_set_cgi_timeout(user_manager_t *mgr, const char *username, int timeout) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET cgi_timeout = ?, 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, timeout);
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_set_cgi_max_output(user_manager_t *mgr, const char *username, size_t max_output) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET cgi_max_output = ?, 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_int64(stmt, 1, max_output);
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_set_webserve_enabled(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 webserve_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_set_directory_listing(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 directory_listing = ?, 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_set_default_index(user_manager_t *mgr, const char *username, const char *index) {
if (!mgr || !username || !index) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET default_index = ?, 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, index, -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_set_permission(user_manager_t *mgr, const char *username,
int perm_read, int perm_write, int perm_execute) {
if (!mgr || !username) return NWEDAV_ERROR;
pal_mutex_lock(&mgr->mutex);
sqlite3_stmt *stmt;
const char *sql = "UPDATE users SET perm_read = ?, perm_write = ?, perm_execute = ?, 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, perm_read);
sqlite3_bind_int(stmt, 2, perm_write);
sqlite3_bind_int(stmt, 3, perm_execute);
sqlite3_bind_int64(stmt, 4, time(NULL));
sqlite3_bind_text(stmt, 5, 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_get_permission(user_manager_t *mgr, const char *username,
int *perm_read, int *perm_write, int *perm_execute) {
if (!mgr || !username) return NWEDAV_ERROR;
user_t user;
int ret = user_manager_get_by_username(mgr, username, &user);
if (ret != NWEDAV_OK) return ret;
if (perm_read) *perm_read = user.perm_read;
if (perm_write) *perm_write = user.perm_write;
if (perm_execute) *perm_execute = user.perm_execute;
return NWEDAV_OK;
}
+36
View File
@@ -5,18 +5,31 @@
#include "../platform/pal.h"
#include <sqlite3.h>
#define SCHEMA_VERSION 2
typedef struct {
uint32_t id;
char username[64];
char email[256];
char display_name[256];
char home_directory[PATH_MAX];
char custom_directory[PATH_MAX];
int use_custom_directory;
uint64_t quota_bytes;
uint64_t used_bytes;
int enabled;
int64_t created_at;
int64_t updated_at;
int64_t last_login;
int cgi_enabled;
int cgi_timeout;
size_t cgi_max_output;
int webserve_enabled;
int directory_listing;
char default_index[64];
int perm_read;
int perm_write;
int perm_execute;
} user_t;
typedef struct {
@@ -50,9 +63,32 @@ int user_manager_group_add(user_manager_t *mgr, const char *name, const char *de
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);
typedef int (*group_callback_t)(group_t *group, void *user_data);
int user_manager_group_list(user_manager_t *mgr, group_callback_t callback, void *user_data);
int user_manager_group_members(user_manager_t *mgr, const char *group_name,
user_callback_t callback, void *user_data);
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);
int user_manager_set_custom_directory(user_manager_t *mgr, const char *username,
const char *directory);
int user_manager_clear_custom_directory(user_manager_t *mgr, const char *username);
int user_manager_set_cgi_enabled(user_manager_t *mgr, const char *username, int enabled);
int user_manager_set_cgi_timeout(user_manager_t *mgr, const char *username, int timeout);
int user_manager_set_cgi_max_output(user_manager_t *mgr, const char *username, size_t max_output);
int user_manager_set_webserve_enabled(user_manager_t *mgr, const char *username, int enabled);
int user_manager_set_directory_listing(user_manager_t *mgr, const char *username, int enabled);
int user_manager_set_default_index(user_manager_t *mgr, const char *username, const char *index);
int user_manager_set_permission(user_manager_t *mgr, const char *username,
int perm_read, int perm_write, int perm_execute);
int user_manager_get_permission(user_manager_t *mgr, const char *username,
int *perm_read, int *perm_write, int *perm_execute);
sqlite3 *user_manager_get_db(user_manager_t *mgr);
#endif
Binary file not shown.
BIN
View File
Binary file not shown.
+16
View File
@@ -16,6 +16,22 @@ typedef struct {
char username[64];
uint64_t quota_bytes;
uint64_t used_bytes;
char home_directory[PATH_MAX];
char custom_directory[PATH_MAX];
int use_custom_directory;
int cgi_enabled;
int cgi_timeout;
size_t cgi_max_output;
int webserve_enabled;
int directory_listing;
char default_index[64];
int perm_read;
int perm_write;
int perm_execute;
char client_ip[64];
int server_port;
char server_name[256];
socket_t client_fd;
} webdav_context_t;
typedef int (*webdav_method_handler_t)(webdav_context_t *ctx,
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+179
View File
@@ -0,0 +1,179 @@
cmake_minimum_required(VERSION 3.10)
set(TEST_FRAMEWORK_SOURCES
test_framework.c
fixtures/fixtures.c
fixtures/http_request_builder.c
)
set(UNIT_TEST_SOURCES
unit/test_http_parser.c
unit/test_http_response.c
unit/test_auth_basic.c
unit/test_auth_digest.c
unit/test_storage_path.c
unit/test_user_database.c
unit/test_lock_manager.c
)
set(INTEGRATION_WEBDAV_SOURCES
integration/webdav_methods/test_options.c
integration/webdav_methods/test_get.c
integration/webdav_methods/test_put.c
integration/webdav_methods/test_delete.c
integration/webdav_methods/test_mkcol.c
integration/webdav_methods/test_copy.c
integration/webdav_methods/test_move.c
integration/webdav_methods/test_propfind.c
integration/webdav_methods/test_proppatch.c
integration/webdav_methods/test_lock.c
integration/webdav_methods/test_unlock.c
)
set(INTEGRATION_HTTP_SOURCES
integration/http_methods/test_head.c
integration/http_methods/test_post.c
)
set(INTEGRATION_AUTH_SOURCES
integration/auth_flows/test_auth_basic_flow.c
integration/auth_flows/test_auth_digest_flow.c
integration/auth_flows/test_401_challenge.c
)
set(MANAGEMENT_SOURCES
management/user_management/test_user_crud.c
management/quota_management/test_quota.c
management/cgi_settings/test_cgi_config.c
)
set(NWEDAV_LIB_SOURCES
../src/platform/pal.c
../src/platform/file.c
../src/platform/time.c
../src/platform/memory.c
../src/core/config.c
../src/core/logging.c
../src/core/server.c
../src/user/usermgr.c
../src/user/password.c
../src/http/parser.c
../src/http/response.c
../src/http/headers.c
../src/http/streaming.c
../src/http/webserve.c
../src/webdav/xml.c
../src/webdav/methods.c
../src/webdav/propfind.c
../src/webdav/proppatch.c
../src/webdav/lock.c
../src/storage/backend.c
../src/storage/properties.c
../src/storage/locks.c
../src/storage/pathmapping.c
../src/auth/auth.c
../src/cgi/cgi.c
../src/concurrency/threadpool.c
../src/concurrency/eventloop.c
../src/concurrency/connpool.c
)
add_executable(run_tests
run_tests.c
${TEST_FRAMEWORK_SOURCES}
${UNIT_TEST_SOURCES}
${NWEDAV_LIB_SOURCES}
)
target_include_directories(run_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src
${SQLITE3_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIRS}
)
target_link_libraries(run_tests
Threads::Threads
${SQLITE3_LIBRARIES}
${OPENSSL_LIBRARIES}
${PLATFORM_LIBS}
)
if(UUID_FOUND)
target_include_directories(run_tests PRIVATE ${UUID_INCLUDE_DIRS})
target_link_libraries(run_tests ${UUID_LIBRARIES})
endif()
# Integration tests disabled - need missing headers (request.h, handler.h) implemented
# add_executable(run_integration_tests
# run_tests.c
# ${TEST_FRAMEWORK_SOURCES}
# ${INTEGRATION_WEBDAV_SOURCES}
# ${INTEGRATION_HTTP_SOURCES}
# ${INTEGRATION_AUTH_SOURCES}
# ${NWEDAV_LIB_SOURCES}
# )
add_executable(run_management_tests
run_tests.c
${TEST_FRAMEWORK_SOURCES}
${MANAGEMENT_SOURCES}
${NWEDAV_LIB_SOURCES}
)
target_include_directories(run_management_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src
${SQLITE3_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIRS}
)
target_link_libraries(run_management_tests
Threads::Threads
${SQLITE3_LIBRARIES}
${OPENSSL_LIBRARIES}
${PLATFORM_LIBS}
)
if(UUID_FOUND)
target_include_directories(run_management_tests PRIVATE ${UUID_INCLUDE_DIRS})
target_link_libraries(run_management_tests ${UUID_LIBRARIES})
endif()
add_executable(run_all_tests
run_tests.c
${TEST_FRAMEWORK_SOURCES}
${UNIT_TEST_SOURCES}
${MANAGEMENT_SOURCES}
${NWEDAV_LIB_SOURCES}
)
target_include_directories(run_all_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src
${SQLITE3_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIRS}
)
target_link_libraries(run_all_tests
Threads::Threads
${SQLITE3_LIBRARIES}
${OPENSSL_LIBRARIES}
${PLATFORM_LIBS}
)
if(UUID_FOUND)
target_include_directories(run_all_tests PRIVATE ${UUID_INCLUDE_DIRS})
target_link_libraries(run_all_tests ${UUID_LIBRARIES})
endif()
add_test(NAME unit_tests COMMAND run_tests)
add_test(NAME management_tests COMMAND run_management_tests)
add_test(NAME all_tests COMMAND run_all_tests)
set_tests_properties(unit_tests PROPERTIES LABELS "unit")
set_tests_properties(management_tests PROPERTIES LABELS "management")
set_tests_properties(all_tests PROPERTIES LABELS "all")
+203
View File
@@ -0,0 +1,203 @@
// retoor <retoor@molodetz.nl>
#include "fixtures.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
static int fixture_counter = 0;
int fixture_create_temp_dir(char *path, size_t size) {
snprintf(path, size, "/tmp/nwedav_test_%d_%ld_%d", getpid(), (long)time(NULL), fixture_counter++);
return mkdir(path, 0755);
}
static int remove_directory_recursive(const char *path) {
DIR *dir = opendir(path);
if (!dir) return -1;
struct dirent *entry;
char full_path[PATH_MAX];
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
remove_directory_recursive(full_path);
} else {
unlink(full_path);
}
}
}
closedir(dir);
return rmdir(path);
}
int fixture_remove_temp_dir(const char *path) {
return remove_directory_recursive(path);
}
int fixture_create_file(const char *path, const char *content, size_t len) {
FILE *f = fopen(path, "wb");
if (!f) return -1;
if (content && len > 0) {
fwrite(content, 1, len, f);
}
fclose(f);
return 0;
}
int fixture_file_exists(const char *path) {
struct stat st;
return stat(path, &st) == 0;
}
int fixture_read_file(const char *path, char *buf, size_t size) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
size_t n = fread(buf, 1, size - 1, f);
buf[n] = '\0';
fclose(f);
return (int)n;
}
test_fixture_t *fixture_create(void) {
test_fixture_t *fixture = calloc(1, sizeof(test_fixture_t));
if (!fixture) return NULL;
if (fixture_create_temp_dir(fixture->temp_dir, sizeof(fixture->temp_dir)) != 0) {
free(fixture);
return NULL;
}
snprintf(fixture->db_path, sizeof(fixture->db_path), "%s/test.db", fixture->temp_dir);
fixture->user_mgr = user_manager_create(fixture->db_path);
if (!fixture->user_mgr) {
fixture_remove_temp_dir(fixture->temp_dir);
free(fixture);
return NULL;
}
char locks_path[PATH_MAX];
snprintf(locks_path, sizeof(locks_path), "%s/locks.db", fixture->temp_dir);
fixture->locks = lock_manager_create(locks_path);
char props_path[PATH_MAX];
snprintf(props_path, sizeof(props_path), "%s/props.db", fixture->temp_dir);
fixture->props = property_store_create(props_path);
return fixture;
}
void fixture_destroy(test_fixture_t *fixture) {
if (!fixture) return;
if (fixture->pathmapping) {
pathmapping_destroy(fixture->pathmapping);
}
if (fixture->locks) {
lock_manager_destroy(fixture->locks);
}
if (fixture->properties) {
property_store_destroy(fixture->properties);
}
if (fixture->props) {
property_store_destroy(fixture->props);
}
if (fixture->storage) {
storage_destroy(fixture->storage);
}
if (fixture->user_mgr) {
user_manager_destroy(fixture->user_mgr);
}
if (fixture->temp_dir[0]) {
fixture_remove_temp_dir(fixture->temp_dir);
}
free(fixture);
}
test_fixture_t *fixture_create_with_user(const char *username, const char *password) {
test_fixture_t *fixture = fixture_create();
if (!fixture) return NULL;
if (user_manager_add(fixture->user_mgr, username, password,
"test@example.com", 10 * 1024 * 1024) != NWEDAV_OK) {
fixture_destroy(fixture);
return NULL;
}
return fixture;
}
test_fixture_t *fixture_create_with_storage(void) {
test_fixture_t *fixture = fixture_create();
if (!fixture) return NULL;
char storage_path[PATH_MAX];
snprintf(storage_path, sizeof(storage_path), "%s/storage", fixture->temp_dir);
mkdir(storage_path, 0755);
fixture->storage = storage_create(storage_path);
if (!fixture->storage) {
fixture_destroy(fixture);
return NULL;
}
char props_path[PATH_MAX];
snprintf(props_path, sizeof(props_path), "%s/props.db", fixture->temp_dir);
fixture->properties = property_store_create(props_path);
char locks_path[PATH_MAX];
snprintf(locks_path, sizeof(locks_path), "%s/locks.db", fixture->temp_dir);
fixture->locks = lock_manager_create(locks_path);
sqlite3 *db = user_manager_get_db(fixture->user_mgr);
if (db) {
fixture->pathmapping = pathmapping_create(db);
}
return fixture;
}
webdav_context_t fixture_create_context(test_fixture_t *fixture, uint32_t user_id) {
webdav_context_t ctx = {0};
ctx.storage = fixture->storage;
ctx.properties = fixture->properties;
ctx.locks = fixture->locks;
ctx.user_id = user_id;
ctx.quota_bytes = 10 * 1024 * 1024;
ctx.used_bytes = 0;
ctx.perm_read = 1;
ctx.perm_write = 1;
ctx.perm_execute = 0;
user_t user;
if (user_manager_get_by_id(fixture->user_mgr, user_id, &user) == NWEDAV_OK) {
strncpy(ctx.username, user.username, sizeof(ctx.username) - 1);
ctx.quota_bytes = user.quota_bytes;
ctx.used_bytes = user.used_bytes;
ctx.perm_read = user.perm_read;
ctx.perm_write = user.perm_write;
ctx.perm_execute = user.perm_execute;
}
return ctx;
}
+39
View File
@@ -0,0 +1,39 @@
// retoor <retoor@molodetz.nl>
#ifndef TEST_FIXTURES_H
#define TEST_FIXTURES_H
#include "nwedav.h"
#include "../../src/user/usermgr.h"
#include "../../src/storage/backend.h"
#include "../../src/storage/properties.h"
#include "../../src/storage/locks.h"
#include "../../src/storage/pathmapping.h"
#include "../../src/auth/auth.h"
#include "../../src/webdav/methods.h"
typedef struct {
user_manager_t *user_mgr;
storage_backend_t *storage;
property_store_t *properties;
property_store_t *props;
lock_manager_t *locks;
path_mapping_manager_t *pathmapping;
char temp_dir[PATH_MAX];
char db_path[PATH_MAX];
} test_fixture_t;
test_fixture_t *fixture_create(void);
void fixture_destroy(test_fixture_t *fixture);
test_fixture_t *fixture_create_with_user(const char *username, const char *password);
test_fixture_t *fixture_create_with_storage(void);
webdav_context_t fixture_create_context(test_fixture_t *fixture, uint32_t user_id);
int fixture_create_temp_dir(char *path, size_t size);
int fixture_remove_temp_dir(const char *path);
int fixture_create_file(const char *path, const char *content, size_t len);
int fixture_file_exists(const char *path);
int fixture_read_file(const char *path, char *buf, size_t size);
#endif
+197
View File
@@ -0,0 +1,197 @@
// retoor <retoor@molodetz.nl>
#include "http_request_builder.h"
#include "../../src/platform/memory.h"
#include "../../src/auth/auth.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
http_request_t *http_request_new(const char *method, const char *uri) {
http_request_t *req = calloc(1, sizeof(http_request_t));
if (!req) return NULL;
http_request_init(req);
req->method = http_method_from_string(method);
req->version_major = 1;
req->version_minor = 1;
if (uri) {
if (req->uri) free(req->uri);
req->uri = strdup(uri);
}
req->depth = -1;
req->overwrite = 1;
return req;
}
void http_request_set_body(http_request_t *req, const char *body, size_t len) {
if (!req) return;
if (req->body) {
free(req->body);
req->body = NULL;
}
req->body_len = 0;
req->content_length = 0;
if (body && len > 0) {
req->body = malloc(len + 1);
if (req->body) {
memcpy(req->body, body, len);
req->body[len] = '\0';
req->body_len = len;
req->content_length = len;
}
}
}
char *http_build_basic_auth(const char *user, const char *pass) {
if (!user || !pass) return NULL;
size_t cred_len = strlen(user) + 1 + strlen(pass);
char *credentials = malloc(cred_len + 1);
if (!credentials) return NULL;
snprintf(credentials, cred_len + 1, "%s:%s", user, pass);
char *encoded = auth_base64_encode((unsigned char *)credentials, cred_len);
free(credentials);
if (!encoded) return NULL;
size_t auth_len = 6 + strlen(encoded) + 1;
char *auth_header = malloc(auth_len);
if (!auth_header) {
free(encoded);
return NULL;
}
snprintf(auth_header, auth_len, "Basic %s", encoded);
free(encoded);
return auth_header;
}
void http_request_set_auth_basic(http_request_t *req, const char *user, const char *pass) {
if (!req || !user || !pass) return;
char *auth_header = http_build_basic_auth(user, pass);
if (auth_header) {
if (req->authorization) free(req->authorization);
req->authorization = auth_header;
http_add_header(req, "Authorization", auth_header);
}
}
void http_request_set_auth_digest(http_request_t *req, const char *user,
const char *realm, const char *nonce,
const char *uri, const char *response) {
if (!req || !user) return;
char header[1024];
snprintf(header, sizeof(header),
"Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", "
"uri=\"%s\", response=\"%s\", qop=auth, nc=00000001, cnonce=\"abc123\"",
user, realm ? realm : "", nonce ? nonce : "",
uri ? uri : "", response ? response : "");
if (req->authorization) free(req->authorization);
req->authorization = strdup(header);
http_add_header(req, "Authorization", header);
}
void http_request_set_destination(http_request_t *req, const char *dest) {
if (!req) return;
if (req->destination) free(req->destination);
req->destination = dest ? strdup(dest) : NULL;
if (dest) http_add_header(req, "Destination", dest);
}
void http_request_set_depth(http_request_t *req, int depth) {
if (!req) return;
req->depth = depth;
char depth_str[16];
if (depth < 0) {
strcpy(depth_str, "infinity");
} else {
snprintf(depth_str, sizeof(depth_str), "%d", depth);
}
http_add_header(req, "Depth", depth_str);
}
void http_request_set_overwrite(http_request_t *req, int overwrite) {
if (!req) return;
req->overwrite = overwrite;
http_add_header(req, "Overwrite", overwrite ? "T" : "F");
}
void http_request_set_lock_token(http_request_t *req, const char *token) {
if (!req) return;
if (req->lock_token) free(req->lock_token);
req->lock_token = token ? strdup(token) : NULL;
if (token) {
char header[512];
snprintf(header, sizeof(header), "<%s>", token);
http_add_header(req, "Lock-Token", header);
}
}
void http_request_set_if_match(http_request_t *req, const char *etag) {
if (req && etag) {
http_add_header(req, "If-Match", etag);
}
}
void http_request_set_if_none_match(http_request_t *req, const char *etag) {
if (req && etag) {
http_add_header(req, "If-None-Match", etag);
}
}
void http_request_set_range(http_request_t *req, int64_t start, int64_t end) {
if (!req) return;
char range[64];
if (end >= 0) {
snprintf(range, sizeof(range), "bytes=%lld-%lld",
(long long)start, (long long)end);
} else {
snprintf(range, sizeof(range), "bytes=%lld-", (long long)start);
}
http_add_header(req, "Range", range);
}
void http_request_destroy(http_request_t *req) {
if (!req) return;
http_request_free(req);
free(req);
}
http_response_t *http_response_new(void) {
http_response_t *res = calloc(1, sizeof(http_response_t));
if (res) {
http_response_init(res);
}
return res;
}
const char *http_response_get_header_value(http_response_t *res, const char *name) {
if (!res || !name) return NULL;
response_header_t *h = res->headers;
while (h) {
if (strcasecmp(h->name, name) == 0) {
return h->value;
}
h = h->next;
}
return NULL;
}
void http_response_destroy(http_response_t *res) {
if (!res) return;
http_response_free(res);
free(res);
}
+30
View File
@@ -0,0 +1,30 @@
// retoor <retoor@molodetz.nl>
#ifndef HTTP_REQUEST_BUILDER_H
#define HTTP_REQUEST_BUILDER_H
#include "../../src/http/parser.h"
#include "../../src/http/response.h"
#include "../../src/auth/auth.h"
http_request_t *http_request_new(const char *method, const char *uri);
void http_request_set_body(http_request_t *req, const char *body, size_t len);
void http_request_set_auth_basic(http_request_t *req, const char *user, const char *pass);
void http_request_set_auth_digest(http_request_t *req, const char *user,
const char *realm, const char *nonce,
const char *uri, const char *response);
void http_request_set_destination(http_request_t *req, const char *dest);
void http_request_set_depth(http_request_t *req, int depth);
void http_request_set_overwrite(http_request_t *req, int overwrite);
void http_request_set_lock_token(http_request_t *req, const char *token);
void http_request_set_if_match(http_request_t *req, const char *etag);
void http_request_set_if_none_match(http_request_t *req, const char *etag);
void http_request_set_range(http_request_t *req, int64_t start, int64_t end);
void http_request_destroy(http_request_t *req);
http_response_t *http_response_new(void);
const char *http_response_get_header_value(http_response_t *res, const char *name);
void http_response_destroy(http_response_t *res);
char *http_build_basic_auth(const char *user, const char *pass);
#endif
@@ -0,0 +1,154 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(auth_401_challenge)
TEST(no_auth_header_returns_401) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute(ctx, "GET", "/protected.txt", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(401_includes_www_authenticate_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute(ctx, "GET", "/protected.txt", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "WWW-Authenticate", NULL));
endpoint_test_destroy(ctx);
}
TEST(401_challenge_includes_realm) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
endpoint_test_execute(ctx, "GET", "/file.txt", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
const char *www_auth = endpoint_response_get_header(ctx, "WWW-Authenticate");
ASSERT_NOT_NULL(www_auth);
ASSERT_TRUE(strstr(www_auth, "realm=") != NULL);
endpoint_test_destroy(ctx);
}
TEST(401_for_put_without_auth) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "PUT", "/newfile.txt", "content", 7);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(401_for_delete_without_auth) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
endpoint_test_execute(ctx, "DELETE", "/file.txt", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(401_for_mkcol_without_auth) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "MKCOL", "/newfolder", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(401_for_proppatch_without_auth) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
const char *body =
"<?xml version=\"1.0\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<set><prop><displayname>x</displayname></prop></set>"
"</propertyupdate>";
endpoint_test_execute(ctx, "PROPPATCH", "/file.txt", body, strlen(body));
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(malformed_auth_header_returns_401) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "GET", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", "InvalidScheme xyz123");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(empty_auth_header_returns_401) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "GET", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", "");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(options_allowed_without_auth) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "OPTIONS", "/", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,138 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(auth_basic_flow)
TEST(basic_auth_valid_credentials) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "testuser", "testpass",
"test@example.com", 0);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute_with_auth(ctx, "GET", "/protected.txt",
"testuser", "testpass", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_invalid_password) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "testuser", "correctpass",
"test@example.com", 0);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute_with_auth(ctx, "GET", "/protected.txt",
"testuser", "wrongpass", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_unknown_user) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute_with_auth(ctx, "GET", "/protected.txt",
"unknown", "anypass", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_disabled_user) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "disabled", "password",
"disabled@example.com", 0);
user_manager_enable(ctx->fixture->user_mgr, "disabled", 0);
storage_write_file(ctx->fixture->storage, "/protected.txt", "secret", 6);
endpoint_test_execute_with_auth(ctx, "GET", "/protected.txt",
"disabled", "password", NULL, 0);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_put_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "writer", "writepass",
"writer@example.com", 0);
const char *content = "new file content";
endpoint_test_execute_with_auth(ctx, "PUT", "/newfile.txt",
"writer", "writepass",
content, strlen(content));
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_mkcol) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "creator", "createpass",
"creator@example.com", 0);
endpoint_test_execute_with_auth(ctx, "MKCOL", "/newfolder",
"creator", "createpass", NULL, 0);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_delete) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "deleter", "deletepass",
"deleter@example.com", 0);
storage_write_file(ctx->fixture->storage, "/todelete.txt", "x", 1);
endpoint_test_execute_with_auth(ctx, "DELETE", "/todelete.txt",
"deleter", "deletepass", NULL, 0);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(basic_auth_special_chars_in_password) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "special", "p@ss:w0rd!#$",
"special@example.com", 0);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
endpoint_test_execute_with_auth(ctx, "GET", "/file.txt",
"special", "p@ss:w0rd!#$", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,169 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
#include "../../src/auth/auth.h"
TEST_SUITE(auth_digest_flow)
static void compute_digest_response(const char *username, const char *password,
const char *realm, const char *nonce,
const char *method, const char *uri,
const char *nc, const char *cnonce,
const char *qop, char *out_response) {
char ha1[65], ha2[65];
char a1[512];
snprintf(a1, sizeof(a1), "%s:%s:%s", username, realm, password);
auth_md5_hex(a1, strlen(a1), ha1);
char a2[512];
snprintf(a2, sizeof(a2), "%s:%s", method, uri);
auth_md5_hex(a2, strlen(a2), ha2);
char response_str[1024];
snprintf(response_str, sizeof(response_str), "%s:%s:%s:%s:%s:%s",
ha1, nonce, nc, cnonce, qop, ha2);
auth_md5_hex(response_str, strlen(response_str), out_response);
}
TEST(digest_auth_valid_credentials) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "digestuser", "digestpass",
"digest@example.com", 0);
storage_write_file(ctx->fixture->storage, "/secure.txt", "secret data", 11);
const char *realm = "WebDAV";
const char *nonce = "abc123nonce";
const char *uri = "/secure.txt";
const char *nc = "00000001";
const char *cnonce = "xyz789cnonce";
const char *qop = "auth";
char response[65];
compute_digest_response("digestuser", "digestpass", realm, nonce,
"GET", uri, nc, cnonce, qop, response);
char auth_header[1024];
snprintf(auth_header, sizeof(auth_header),
"Digest username=\"digestuser\", realm=\"%s\", nonce=\"%s\", "
"uri=\"%s\", nc=%s, cnonce=\"%s\", qop=%s, response=\"%s\"",
realm, nonce, uri, nc, cnonce, qop, response);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "GET", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, uri, sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", auth_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(digest_auth_invalid_response) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "testuser", "testpass",
"test@example.com", 0);
storage_write_file(ctx->fixture->storage, "/file.txt", "data", 4);
char auth_header[1024];
snprintf(auth_header, sizeof(auth_header),
"Digest username=\"testuser\", realm=\"WebDAV\", nonce=\"abc123\", "
"uri=\"/file.txt\", nc=00000001, cnonce=\"xyz789\", qop=auth, "
"response=\"invalidresponse\"");
http_request_init(&ctx->request);
strncpy(ctx->request.method, "GET", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", auth_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(digest_auth_unknown_user) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "data", 4);
char response[65];
compute_digest_response("unknown", "anypass", "WebDAV", "nonce123",
"GET", "/file.txt", "00000001", "cnonce", "auth", response);
char auth_header[1024];
snprintf(auth_header, sizeof(auth_header),
"Digest username=\"unknown\", realm=\"WebDAV\", nonce=\"nonce123\", "
"uri=\"/file.txt\", nc=00000001, cnonce=\"cnonce\", qop=auth, "
"response=\"%s\"", response);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "GET", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", auth_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_401_UNAUTHORIZED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(digest_auth_put_operation) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
user_manager_add(ctx->fixture->user_mgr, "writer", "writepass",
"writer@example.com", 0);
const char *realm = "WebDAV";
const char *nonce = "putnonce";
const char *uri = "/newfile.txt";
const char *nc = "00000001";
const char *cnonce = "putcnonce";
const char *qop = "auth";
char response[65];
compute_digest_response("writer", "writepass", realm, nonce,
"PUT", uri, nc, cnonce, qop, response);
char auth_header[1024];
snprintf(auth_header, sizeof(auth_header),
"Digest username=\"writer\", realm=\"%s\", nonce=\"%s\", "
"uri=\"%s\", nc=%s, cnonce=\"%s\", qop=%s, response=\"%s\"",
realm, nonce, uri, nc, cnonce, qop, response);
const char *content = "file content";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PUT", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, uri, sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Authorization", auth_header);
ctx->request.body = strdup(content);
ctx->request.body_len = strlen(content);
ctx->request.content_length = strlen(content);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
+155
View File
@@ -0,0 +1,155 @@
// retoor <retoor@molodetz.nl>
#ifndef HTTP_ENDPOINTS_H
#define HTTP_ENDPOINTS_H
#include "../fixtures/fixtures.h"
#include "../fixtures/http_request_builder.h"
#include "../../src/http/request.h"
#include "../../src/http/response.h"
#include "../../src/webdav/handler.h"
typedef struct {
test_fixture_t *fixture;
http_request_t request;
http_response_t response;
char *raw_request;
} endpoint_test_ctx_t;
static inline endpoint_test_ctx_t *endpoint_test_create(void) {
endpoint_test_ctx_t *ctx = calloc(1, sizeof(endpoint_test_ctx_t));
if (!ctx) return NULL;
ctx->fixture = fixture_create_with_storage();
if (!ctx->fixture) {
free(ctx);
return NULL;
}
http_request_init(&ctx->request);
http_response_init(&ctx->response);
ctx->raw_request = NULL;
return ctx;
}
static inline void endpoint_test_destroy(endpoint_test_ctx_t *ctx) {
if (!ctx) return;
http_request_free(&ctx->request);
http_response_free(&ctx->response);
if (ctx->raw_request) free(ctx->raw_request);
fixture_destroy(ctx->fixture);
free(ctx);
}
static inline int endpoint_test_execute(endpoint_test_ctx_t *ctx,
const char *method,
const char *path,
const char *body,
size_t body_len) {
http_request_free(&ctx->request);
http_request_init(&ctx->request);
strncpy(ctx->request.method, method, sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, path, sizeof(ctx->request.uri) - 1);
strncpy(ctx->request.version, "HTTP/1.1", sizeof(ctx->request.version) - 1);
if (body && body_len > 0) {
ctx->request.body = malloc(body_len + 1);
if (ctx->request.body) {
memcpy(ctx->request.body, body, body_len);
ctx->request.body[body_len] = '\0';
ctx->request.body_len = body_len;
ctx->request.content_length = body_len;
}
}
http_response_free(&ctx->response);
http_response_init(&ctx->response);
return webdav_handle_request(ctx->fixture->storage,
ctx->fixture->locks,
ctx->fixture->props,
&ctx->request,
&ctx->response);
}
static inline int endpoint_test_execute_with_auth(endpoint_test_ctx_t *ctx,
const char *method,
const char *path,
const char *username,
const char *password,
const char *body,
size_t body_len) {
http_request_free(&ctx->request);
http_request_init(&ctx->request);
strncpy(ctx->request.method, method, sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, path, sizeof(ctx->request.uri) - 1);
strncpy(ctx->request.version, "HTTP/1.1", sizeof(ctx->request.version) - 1);
if (username && password) {
char credentials[256];
snprintf(credentials, sizeof(credentials), "%s:%s", username, password);
char *encoded = auth_base64_encode((unsigned char *)credentials, strlen(credentials));
if (encoded) {
char auth_header[512];
snprintf(auth_header, sizeof(auth_header), "Basic %s", encoded);
http_request_add_header(&ctx->request, "Authorization", auth_header);
free(encoded);
}
}
if (body && body_len > 0) {
ctx->request.body = malloc(body_len + 1);
if (ctx->request.body) {
memcpy(ctx->request.body, body, body_len);
ctx->request.body[body_len] = '\0';
ctx->request.body_len = body_len;
ctx->request.content_length = body_len;
}
}
http_response_free(&ctx->response);
http_response_init(&ctx->response);
return webdav_handle_request(ctx->fixture->storage,
ctx->fixture->locks,
ctx->fixture->props,
&ctx->request,
&ctx->response);
}
static inline void endpoint_test_add_header(endpoint_test_ctx_t *ctx,
const char *name,
const char *value) {
http_request_add_header(&ctx->request, name, value);
}
static inline int endpoint_response_has_header(endpoint_test_ctx_t *ctx,
const char *name,
const char *expected_value) {
response_header_t *h = ctx->response.headers;
while (h) {
if (strcasecmp(h->name, name) == 0) {
if (expected_value == NULL) return 1;
return strcmp(h->value, expected_value) == 0;
}
h = h->next;
}
return 0;
}
static inline const char *endpoint_response_get_header(endpoint_test_ctx_t *ctx,
const char *name) {
response_header_t *h = ctx->response.headers;
while (h) {
if (strcasecmp(h->name, name) == 0) {
return h->value;
}
h = h->next;
}
return NULL;
}
#endif
+116
View File
@@ -0,0 +1,116 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(http_head)
TEST(head_existing_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "Test content for HEAD";
storage_write_file(ctx->fixture->storage, "/test.txt", content, strlen(content));
endpoint_test_execute(ctx, "HEAD", "/test.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(ctx->response.body == NULL || ctx->response.body_len == 0);
endpoint_test_destroy(ctx);
}
TEST(head_includes_content_length) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "exactly 25 bytes of data!";
storage_write_file(ctx->fixture->storage, "/sized.txt", content, strlen(content));
endpoint_test_execute(ctx, "HEAD", "/sized.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
const char *cl = endpoint_response_get_header(ctx, "Content-Length");
ASSERT_NOT_NULL(cl);
ASSERT_EQUAL(25, atoi(cl));
endpoint_test_destroy(ctx);
}
TEST(head_includes_content_type) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/page.html", "<html></html>", 13);
endpoint_test_execute(ctx, "HEAD", "/page.html", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "Content-Type", NULL));
endpoint_test_destroy(ctx);
}
TEST(head_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "HEAD", "/nonexistent.txt", NULL, 0);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(head_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
endpoint_test_execute(ctx, "HEAD", "/folder", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(ctx->response.body == NULL || ctx->response.body_len == 0);
endpoint_test_destroy(ctx);
}
TEST(head_root) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "HEAD", "/", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(head_includes_etag) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/etag.txt", "content", 7);
endpoint_test_execute(ctx, "HEAD", "/etag.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "ETag", NULL));
endpoint_test_destroy(ctx);
}
TEST(head_includes_last_modified) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/modified.txt", "data", 4);
endpoint_test_execute(ctx, "HEAD", "/modified.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "Last-Modified", NULL));
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,80 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(http_post)
TEST(post_not_implemented) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "POST", "/", "data=test", 9);
ASSERT_TRUE(ctx->response.status == HTTP_405_METHOD_NOT_ALLOWED ||
ctx->response.status == HTTP_501_NOT_IMPLEMENTED);
endpoint_test_destroy(ctx);
}
TEST(post_to_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "content", 7);
endpoint_test_execute(ctx, "POST", "/file.txt", "posted=data", 11);
ASSERT_TRUE(ctx->response.status == HTTP_405_METHOD_NOT_ALLOWED ||
ctx->response.status == HTTP_501_NOT_IMPLEMENTED);
endpoint_test_destroy(ctx);
}
TEST(post_to_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
endpoint_test_execute(ctx, "POST", "/folder", "data", 4);
ASSERT_TRUE(ctx->response.status == HTTP_405_METHOD_NOT_ALLOWED ||
ctx->response.status == HTTP_501_NOT_IMPLEMENTED);
endpoint_test_destroy(ctx);
}
TEST(post_with_content_type) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "POST", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/x-www-form-urlencoded");
ctx->request.body = strdup("key=value");
ctx->request.body_len = 9;
ctx->request.content_length = 9;
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_TRUE(ctx->response.status == HTTP_405_METHOD_NOT_ALLOWED ||
ctx->response.status == HTTP_501_NOT_IMPLEMENTED);
endpoint_test_destroy(ctx);
}
TEST(post_empty_body) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "POST", "/", NULL, 0);
ASSERT_TRUE(ctx->response.status == HTTP_405_METHOD_NOT_ALLOWED ||
ctx->response.status == HTTP_501_NOT_IMPLEMENTED);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,155 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_copy)
TEST(copy_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "original content";
storage_write_file(ctx->fixture->storage, "/source.txt", content, strlen(content));
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/source.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dest.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/dest.txt");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/source.txt");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(copy_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/srcfolder");
storage_write_file(ctx->fixture->storage, "/srcfolder/file1.txt", "a", 1);
storage_write_file(ctx->fixture->storage, "/srcfolder/file2.txt", "b", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/srcfolder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dstfolder");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/dstfolder");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/dstfolder/file1.txt");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(copy_source_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dest.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(copy_missing_destination_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/source.txt", "data", 4);
endpoint_test_execute(ctx, "COPY", "/source.txt", NULL, 0);
ASSERT_EQUAL(HTTP_400_BAD_REQUEST, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(copy_overwrite_true) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "source", 6);
storage_write_file(ctx->fixture->storage, "/dst.txt", "destination", 11);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dst.txt");
http_request_add_header(&ctx->request, "Overwrite", "T");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(copy_overwrite_false_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "source", 6);
storage_write_file(ctx->fixture->storage, "/dst.txt", "destination", 11);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dst.txt");
http_request_add_header(&ctx->request, "Overwrite", "F");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_412_PRECONDITION_FAILED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(copy_dest_parent_missing) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "data", 4);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "COPY", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/missing/dst.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,132 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_delete)
TEST(delete_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/todelete.txt", "content", 7);
endpoint_test_execute(ctx, "DELETE", "/todelete.txt", NULL, 0);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/todelete.txt");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
TEST(delete_empty_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/emptyfolder");
endpoint_test_execute(ctx, "DELETE", "/emptyfolder", NULL, 0);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/emptyfolder");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
TEST(delete_collection_with_contents) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
storage_write_file(ctx->fixture->storage, "/folder/file1.txt", "a", 1);
storage_write_file(ctx->fixture->storage, "/folder/file2.txt", "b", 1);
storage_create_collection(ctx->fixture->storage, "/folder/sub");
storage_write_file(ctx->fixture->storage, "/folder/sub/deep.txt", "c", 1);
endpoint_test_execute(ctx, "DELETE", "/folder", NULL, 0);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/folder");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
TEST(delete_nonexistent_returns_404) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "DELETE", "/doesnotexist.txt", NULL, 0);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(delete_root_not_allowed) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "DELETE", "/", NULL, 0);
ASSERT_NOT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(delete_locked_resource_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked.txt", "data", 4);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked.txt", &lock);
endpoint_test_execute(ctx, "DELETE", "/locked.txt", NULL, 0);
ASSERT_EQUAL(HTTP_423_LOCKED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(delete_with_valid_lock_token) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked2.txt", "data", 4);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked2.txt", &lock);
char if_header[256];
snprintf(if_header, sizeof(if_header), "(<%s>)", lock.token);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "DELETE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/locked2.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "If", if_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
+132
View File
@@ -0,0 +1,132 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_get)
TEST(get_existing_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "Hello, World!";
storage_write_file(ctx->fixture->storage, "/hello.txt", content, strlen(content));
endpoint_test_execute(ctx, "GET", "/hello.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_NOT_NULL(ctx->response.body);
ASSERT_EQUAL(strlen(content), ctx->response.body_len);
ASSERT_MEM_EQ(content, ctx->response.body, strlen(content));
endpoint_test_destroy(ctx);
}
TEST(get_nonexistent_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "GET", "/nonexistent.txt", NULL, 0);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(get_collection_returns_listing) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
storage_write_file(ctx->fixture->storage, "/folder/file1.txt", "a", 1);
storage_write_file(ctx->fixture->storage, "/folder/file2.txt", "b", 1);
endpoint_test_execute(ctx, "GET", "/folder/", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(get_sets_content_type) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/test.html", "<html></html>", 13);
endpoint_test_execute(ctx, "GET", "/test.html", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "Content-Type", NULL));
endpoint_test_destroy(ctx);
}
TEST(get_sets_content_length) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "exact length test";
storage_write_file(ctx->fixture->storage, "/length.txt", content, strlen(content));
endpoint_test_execute(ctx, "GET", "/length.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
const char *cl = endpoint_response_get_header(ctx, "Content-Length");
ASSERT_NOT_NULL(cl);
ASSERT_EQUAL((int)strlen(content), atoi(cl));
endpoint_test_destroy(ctx);
}
TEST(get_binary_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
unsigned char binary[] = {0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD};
storage_write_file(ctx->fixture->storage, "/binary.bin", (char *)binary, sizeof(binary));
endpoint_test_execute(ctx, "GET", "/binary.bin", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_EQUAL(sizeof(binary), ctx->response.body_len);
ASSERT_MEM_EQ(binary, ctx->response.body, sizeof(binary));
endpoint_test_destroy(ctx);
}
TEST(get_empty_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/empty.txt", "", 0);
endpoint_test_execute(ctx, "GET", "/empty.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_EQUAL(0, ctx->response.body_len);
endpoint_test_destroy(ctx);
}
TEST(get_root_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "GET", "/", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(get_path_traversal_blocked) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "GET", "/../etc/passwd", NULL, 0);
ASSERT_NOT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,229 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_lock)
TEST(lock_file_exclusive) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "content", 7);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><exclusive/></lockscope>"
"<locktype><write/></locktype>"
"<owner><href>http://example.com/user</href></owner>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
http_request_add_header(&ctx->request, "Timeout", "Second-3600");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "Lock-Token", NULL));
endpoint_test_destroy(ctx);
}
TEST(lock_file_shared) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/shared.txt", "x", 1);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><shared/></lockscope>"
"<locktype><write/></locktype>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/shared.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(lock_collection_depth_infinity) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
storage_write_file(ctx->fixture->storage, "/folder/child.txt", "x", 1);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><exclusive/></lockscope>"
"<locktype><write/></locktype>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/folder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "infinity");
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(lock_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><exclusive/></lockscope>"
"<locktype><write/></locktype>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent.txt", sizeof(ctx->request.uri) - 1);
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(lock_already_locked_exclusive) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked.txt", "x", 1);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked.txt", &lock);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><exclusive/></lockscope>"
"<locktype><write/></locktype>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/locked.txt", sizeof(ctx->request.uri) - 1);
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_423_LOCKED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(lock_refresh) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/refresh.txt", "x", 1);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 100;
lock_manager_acquire(ctx->fixture->locks, "/refresh.txt", &lock);
char if_header[256];
snprintf(if_header, sizeof(if_header), "(<%s>)", lock.token);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/refresh.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "If", if_header);
http_request_add_header(&ctx->request, "Timeout", "Second-7200");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(lock_response_contains_lockdiscovery) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/discover.txt", "x", 1);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<lockinfo xmlns=\"DAV:\">"
"<lockscope><exclusive/></lockscope>"
"<locktype><write/></locktype>"
"</lockinfo>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "LOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/discover.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_NOT_NULL(ctx->response.body);
ASSERT_TRUE(strstr(ctx->response.body, "lockdiscovery") != NULL);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,113 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_mkcol)
TEST(mkcol_create_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "MKCOL", "/newcollection", NULL, 0);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/newcollection");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(mkcol_nested_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/parent");
endpoint_test_execute(ctx, "MKCOL", "/parent/child", NULL, 0);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/parent/child");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(mkcol_parent_missing_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "MKCOL", "/missing/child", NULL, 0);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(mkcol_already_exists_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/existing");
endpoint_test_execute(ctx, "MKCOL", "/existing", NULL, 0);
ASSERT_EQUAL(HTTP_405_METHOD_NOT_ALLOWED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(mkcol_file_exists_at_path_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/isfile", "content", 7);
endpoint_test_execute(ctx, "MKCOL", "/isfile", NULL, 0);
ASSERT_EQUAL(HTTP_405_METHOD_NOT_ALLOWED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(mkcol_with_body_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "MKCOL", "/withbody", "some body", 9);
ASSERT_EQUAL(HTTP_415_UNSUPPORTED_MEDIA_TYPE, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(mkcol_root_not_allowed) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "MKCOL", "/", NULL, 0);
ASSERT_NOT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(mkcol_deep_nesting) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/a");
storage_create_collection(ctx->fixture->storage, "/a/b");
storage_create_collection(ctx->fixture->storage, "/a/b/c");
endpoint_test_execute(ctx, "MKCOL", "/a/b/c/d", NULL, 0);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/a/b/c/d");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,209 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_move)
TEST(move_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/original.txt", "content", 7);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/original.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/moved.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/moved.txt");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/original.txt");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
TEST(move_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/oldfolder");
storage_write_file(ctx->fixture->storage, "/oldfolder/file.txt", "data", 4);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/oldfolder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/newfolder");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/newfolder");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/newfolder/file.txt");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/oldfolder");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
TEST(move_source_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dest.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_missing_destination_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/source.txt", "data", 4);
endpoint_test_execute(ctx, "MOVE", "/source.txt", NULL, 0);
ASSERT_EQUAL(HTTP_400_BAD_REQUEST, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_overwrite_true) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "source", 6);
storage_write_file(ctx->fixture->storage, "/dst.txt", "destination", 11);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dst.txt");
http_request_add_header(&ctx->request, "Overwrite", "T");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_overwrite_false_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "source", 6);
storage_write_file(ctx->fixture->storage, "/dst.txt", "destination", 11);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/dst.txt");
http_request_add_header(&ctx->request, "Overwrite", "F");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_412_PRECONDITION_FAILED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_dest_parent_missing) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/src.txt", "data", 4);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/src.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/missing/dst.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_locked_source_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked.txt", "data", 4);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked.txt", &lock);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/locked.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/unlocked.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_423_LOCKED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(move_rename_in_place) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
storage_write_file(ctx->fixture->storage, "/folder/old.txt", "data", 4);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "MOVE", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/folder/old.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Destination", "/folder/new.txt");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/folder/new.txt");
ASSERT_TRUE(exists);
exists = storage_exists(ctx->fixture->storage, "/folder/old.txt");
ASSERT_FALSE(exists);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,83 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_options)
TEST(options_returns_200) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "OPTIONS", "/", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(options_includes_dav_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "OPTIONS", "/", NULL, 0);
ASSERT_TRUE(endpoint_response_has_header(ctx, "DAV", NULL));
endpoint_test_destroy(ctx);
}
TEST(options_includes_allow_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "OPTIONS", "/", NULL, 0);
const char *allow = endpoint_response_get_header(ctx, "Allow");
ASSERT_NOT_NULL(allow);
ASSERT_TRUE(strstr(allow, "GET") != NULL);
ASSERT_TRUE(strstr(allow, "PUT") != NULL);
ASSERT_TRUE(strstr(allow, "DELETE") != NULL);
ASSERT_TRUE(strstr(allow, "PROPFIND") != NULL);
ASSERT_TRUE(strstr(allow, "MKCOL") != NULL);
endpoint_test_destroy(ctx);
}
TEST(options_star_uri) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "OPTIONS", "*", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(options_on_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/testcol");
endpoint_test_execute(ctx, "OPTIONS", "/testcol", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
ASSERT_TRUE(endpoint_response_has_header(ctx, "DAV", NULL));
endpoint_test_destroy(ctx);
}
TEST(options_on_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/testfile.txt", "data", 4);
endpoint_test_execute(ctx, "OPTIONS", "/testfile.txt", NULL, 0);
ASSERT_EQUAL(HTTP_200_OK, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,185 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_propfind)
TEST(propfind_file_depth_0) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/test.txt", "content", 7);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/test.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
ASSERT_NOT_NULL(ctx->response.body);
ASSERT_TRUE(strstr(ctx->response.body, "multistatus") != NULL);
endpoint_test_destroy(ctx);
}
TEST(propfind_collection_depth_0) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/folder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(propfind_collection_depth_1) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
storage_write_file(ctx->fixture->storage, "/folder/file1.txt", "a", 1);
storage_write_file(ctx->fixture->storage, "/folder/file2.txt", "b", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/folder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "1");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
ASSERT_TRUE(strstr(ctx->response.body, "file1.txt") != NULL);
ASSERT_TRUE(strstr(ctx->response.body, "file2.txt") != NULL);
endpoint_test_destroy(ctx);
}
TEST(propfind_collection_depth_infinity) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/root");
storage_create_collection(ctx->fixture->storage, "/root/sub");
storage_write_file(ctx->fixture->storage, "/root/sub/deep.txt", "x", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/root", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "infinity");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
ASSERT_TRUE(strstr(ctx->response.body, "deep.txt") != NULL);
endpoint_test_destroy(ctx);
}
TEST(propfind_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(propfind_allprop) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/props.txt", "content", 7);
const char *body = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propfind xmlns=\"DAV:\"><allprop/></propfind>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/props.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
ASSERT_TRUE(strstr(ctx->response.body, "getcontentlength") != NULL);
endpoint_test_destroy(ctx);
}
TEST(propfind_propname) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/propname.txt", "x", 1);
const char *body = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propfind xmlns=\"DAV:\"><propname/></propfind>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/propname.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(propfind_root) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPFIND", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Depth", "0");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,190 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_proppatch)
TEST(proppatch_set_property) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "content", 7);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<set><prop>"
"<displayname>My File</displayname>"
"</prop></set>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(proppatch_remove_property) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "content", 7);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<remove><prop>"
"<displayname/>"
"</prop></remove>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(proppatch_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<set><prop><displayname>x</displayname></prop></set>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent.txt", sizeof(ctx->request.uri) - 1);
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(proppatch_custom_namespace) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/custom.txt", "x", 1);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\" xmlns:custom=\"http://example.com/ns\">"
"<set><prop>"
"<custom:myprop>myvalue</custom:myprop>"
"</prop></set>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/custom.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(proppatch_on_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<set><prop><displayname>My Folder</displayname></prop></set>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/folder", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Content-Type", "application/xml");
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_207_MULTI_STATUS, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(proppatch_locked_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked.txt", "x", 1);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked.txt", &lock);
const char *body =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<propertyupdate xmlns=\"DAV:\">"
"<set><prop><displayname>x</displayname></prop></set>"
"</propertyupdate>";
http_request_init(&ctx->request);
strncpy(ctx->request.method, "PROPPATCH", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/locked.txt", sizeof(ctx->request.uri) - 1);
ctx->request.body = strdup(body);
ctx->request.body_len = strlen(body);
ctx->request.content_length = strlen(body);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_423_LOCKED, ctx->response.status);
endpoint_test_destroy(ctx);
}
+130
View File
@@ -0,0 +1,130 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_put)
TEST(put_create_new_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
const char *content = "New file content";
endpoint_test_execute(ctx, "PUT", "/newfile.txt", content, strlen(content));
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/newfile.txt");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(put_overwrite_existing_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/existing.txt", "old", 3);
const char *new_content = "new content";
endpoint_test_execute(ctx, "PUT", "/existing.txt", new_content, strlen(new_content));
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(put_parent_missing_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "PUT", "/nonexistent/file.txt", "data", 4);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(put_into_collection) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/folder");
const char *content = "file in folder";
endpoint_test_execute(ctx, "PUT", "/folder/document.txt", content, strlen(content));
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/folder/document.txt");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(put_binary_data) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
unsigned char binary[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
endpoint_test_execute(ctx, "PUT", "/image.png", (char *)binary, sizeof(binary));
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(put_empty_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "PUT", "/empty.txt", "", 0);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
int exists = storage_exists(ctx->fixture->storage, "/empty.txt");
ASSERT_TRUE(exists);
endpoint_test_destroy(ctx);
}
TEST(put_collection_uri_fails) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_create_collection(ctx->fixture->storage, "/mycol");
endpoint_test_execute(ctx, "PUT", "/mycol/", "data", 4);
ASSERT_NOT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(put_path_traversal_blocked) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
endpoint_test_execute(ctx, "PUT", "/../../../tmp/evil.txt", "malicious", 9);
ASSERT_NOT_EQUAL(HTTP_201_CREATED, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(put_large_file) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
size_t size = 1024 * 1024;
char *large_content = malloc(size);
ASSERT_NOT_NULL(large_content);
memset(large_content, 'X', size);
endpoint_test_execute(ctx, "PUT", "/largefile.bin", large_content, size);
ASSERT_EQUAL(HTTP_201_CREATED, ctx->response.status);
free(large_content);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,169 @@
// retoor <retoor@molodetz.nl>
#include "../../test_framework.h"
#include "../http_endpoints.h"
TEST_SUITE(webdav_unlock)
TEST(unlock_success) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/locked.txt", "content", 7);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/locked.txt", &lock);
char lock_token_header[256];
snprintf(lock_token_header, sizeof(lock_token_header), "<%s>", lock.token);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "UNLOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/locked.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Lock-Token", lock_token_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
int is_locked = lock_manager_is_locked(ctx->fixture->locks, "/locked.txt");
ASSERT_FALSE(is_locked);
endpoint_test_destroy(ctx);
}
TEST(unlock_missing_token_header) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/file.txt", &lock);
endpoint_test_execute(ctx, "UNLOCK", "/file.txt", NULL, 0);
ASSERT_EQUAL(HTTP_400_BAD_REQUEST, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(unlock_wrong_token) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/file.txt", "x", 1);
lock_info_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/file.txt", &lock);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "UNLOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/file.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Lock-Token", "<wrong-token>");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(unlock_not_locked) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/notlocked.txt", "x", 1);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "UNLOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/notlocked.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Lock-Token", "<some-token>");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_409_CONFLICT, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(unlock_not_found) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "UNLOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/nonexistent.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Lock-Token", "<some-token>");
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, ctx->response.status);
endpoint_test_destroy(ctx);
}
TEST(unlock_shared_lock) {
endpoint_test_ctx_t *ctx = endpoint_test_create();
ASSERT_NOT_NULL(ctx);
storage_write_file(ctx->fixture->storage, "/shared.txt", "x", 1);
lock_info_t lock1;
memset(&lock1, 0, sizeof(lock1));
strncpy(lock1.owner, "user1", sizeof(lock1.owner) - 1);
lock1.scope = LOCK_SCOPE_SHARED;
lock1.type = LOCK_TYPE_WRITE;
lock1.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/shared.txt", &lock1);
lock_info_t lock2;
memset(&lock2, 0, sizeof(lock2));
strncpy(lock2.owner, "user2", sizeof(lock2.owner) - 1);
lock2.scope = LOCK_SCOPE_SHARED;
lock2.type = LOCK_TYPE_WRITE;
lock2.timeout = 3600;
lock_manager_acquire(ctx->fixture->locks, "/shared.txt", &lock2);
char lock_token_header[256];
snprintf(lock_token_header, sizeof(lock_token_header), "<%s>", lock1.token);
http_request_init(&ctx->request);
strncpy(ctx->request.method, "UNLOCK", sizeof(ctx->request.method) - 1);
strncpy(ctx->request.uri, "/shared.txt", sizeof(ctx->request.uri) - 1);
http_request_add_header(&ctx->request, "Lock-Token", lock_token_header);
http_response_init(&ctx->response);
webdav_handle_request(ctx->fixture->storage, ctx->fixture->locks,
ctx->fixture->props, &ctx->request, &ctx->response);
ASSERT_EQUAL(HTTP_204_NO_CONTENT, ctx->response.status);
int is_locked = lock_manager_is_locked(ctx->fixture->locks, "/shared.txt");
ASSERT_TRUE(is_locked);
endpoint_test_destroy(ctx);
}
@@ -0,0 +1,93 @@
#include "../../test_framework.h"
#include "../../fixtures/fixtures.h"
#include "../../../src/cgi/cgi.h"
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
TEST_SUITE(cgi_config)
TEST(cgi_config_init_defaults) {
cgi_config_t config;
cgi_config_init(&config);
ASSERT_EQUAL(0, config.enabled);
ASSERT_EQUAL(30, config.timeout_seconds);
ASSERT_TRUE(config.max_output_bytes > 0);
ASSERT_TRUE(config.buffer_size > 0);
}
TEST(cgi_config_custom_values) {
cgi_config_t config;
cgi_config_init(&config);
config.enabled = 0;
config.timeout_seconds = 60;
config.max_output_bytes = 1024 * 1024;
strncpy(config.client_ip, "192.168.1.1", sizeof(config.client_ip) - 1);
strncpy(config.server_name, "test.local", sizeof(config.server_name) - 1);
config.server_port = 8080;
ASSERT_EQUAL(0, config.enabled);
ASSERT_EQUAL(60, config.timeout_seconds);
ASSERT_EQUAL(1024 * 1024, (int)config.max_output_bytes);
ASSERT_STR_EQ("192.168.1.1", config.client_ip);
ASSERT_STR_EQ("test.local", config.server_name);
ASSERT_EQUAL(8080, config.server_port);
}
TEST(cgi_is_executable_nonexistent) {
int result = cgi_is_executable("/nonexistent/script.cgi");
ASSERT_FALSE(result);
}
TEST(cgi_is_executable_regular_file) {
char temp_path[] = "/tmp/test_cgi_XXXXXX";
int fd = mkstemp(temp_path);
ASSERT_TRUE(fd >= 0);
close(fd);
int result = cgi_is_executable(temp_path);
ASSERT_FALSE(result);
unlink(temp_path);
}
TEST(cgi_is_executable_executable_file) {
char temp_path[] = "/tmp/test_cgi_XXXXXX";
int fd = mkstemp(temp_path);
ASSERT_TRUE(fd >= 0);
close(fd);
chmod(temp_path, 0755);
int result = cgi_is_executable(temp_path);
ASSERT_TRUE(result);
unlink(temp_path);
}
TEST(cgi_detect_headers_with_headers) {
const char *output = "Content-Type: text/html\r\n\r\n<html></html>";
int has_headers = cgi_detect_headers(output, strlen(output));
ASSERT_TRUE(has_headers);
}
TEST(cgi_detect_headers_without_headers) {
const char *output = "Just plain output without headers";
int has_headers = cgi_detect_headers(output, strlen(output));
ASSERT_FALSE(has_headers);
}
TEST(cgi_detect_headers_status_header) {
const char *output = "Status: 200 OK\r\nContent-Type: text/plain\r\n\r\nBody";
int has_headers = cgi_detect_headers(output, strlen(output));
ASSERT_TRUE(has_headers);
}
TEST(cgi_detect_headers_empty) {
const char *output = "";
int has_headers = cgi_detect_headers(output, 0);
ASSERT_FALSE(has_headers);
}
@@ -0,0 +1,107 @@
#include "../../test_framework.h"
#include "../../fixtures/fixtures.h"
#include "../../../src/user/usermgr.h"
#include "../../../src/storage/backend.h"
#include <string.h>
TEST_SUITE(quota_management)
TEST(set_user_quota) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "quotauser", "pass", "q@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "quotauser", &user);
int ret = user_manager_set_quota(fixture->user_mgr, user.id, 10 * 1024 * 1024);
ASSERT_EQUAL(NWEDAV_OK, ret);
user_manager_get_by_username(fixture->user_mgr, "quotauser", &user);
ASSERT_EQUAL(10 * 1024 * 1024, (int)user.quota_bytes);
fixture_destroy(fixture);
}
TEST(quota_zero_means_unlimited) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "unlimited", "pass", "u@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "unlimited", &user);
ASSERT_EQUAL(0, (int)user.quota_bytes);
fixture_destroy(fixture);
}
TEST(quota_get_values) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "testquota", "pass", "tq@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "testquota", &user);
user_manager_set_quota(fixture->user_mgr, user.id, 50000);
user_manager_update_quota_used(fixture->user_mgr, user.id, 10000);
uint64_t quota, used;
int ret = user_manager_get_quota(fixture->user_mgr, user.id, &quota, &used);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(50000, (int)quota);
ASSERT_EQUAL(10000, (int)used);
fixture_destroy(fixture);
}
TEST(quota_update_delta) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "deltauser", "pass", "d@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "deltauser", &user);
user_manager_update_quota_used(fixture->user_mgr, user.id, 1000);
user_manager_update_quota_used(fixture->user_mgr, user.id, 500);
uint64_t quota, used;
user_manager_get_quota(fixture->user_mgr, user.id, &quota, &used);
ASSERT_EQUAL(1500, (int)used);
user_manager_update_quota_used(fixture->user_mgr, user.id, -300);
user_manager_get_quota(fixture->user_mgr, user.id, &quota, &used);
ASSERT_EQUAL(1200, (int)used);
fixture_destroy(fixture);
}
TEST(quota_multiple_users_independent) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "user_a", "p", "a@test.com", 10000);
user_manager_add(fixture->user_mgr, "user_b", "p", "b@test.com", 5000);
user_t user_a, user_b;
user_manager_get_by_username(fixture->user_mgr, "user_a", &user_a);
user_manager_get_by_username(fixture->user_mgr, "user_b", &user_b);
user_manager_update_quota_used(fixture->user_mgr, user_a.id, 8000);
user_manager_update_quota_used(fixture->user_mgr, user_b.id, 1000);
uint64_t quota_a, used_a, quota_b, used_b;
user_manager_get_quota(fixture->user_mgr, user_a.id, &quota_a, &used_a);
user_manager_get_quota(fixture->user_mgr, user_b.id, &quota_b, &used_b);
ASSERT_EQUAL(8000, (int)used_a);
ASSERT_EQUAL(1000, (int)used_b);
fixture_destroy(fixture);
}
@@ -0,0 +1,173 @@
#include "../../test_framework.h"
#include "../../fixtures/fixtures.h"
#include "../../../src/user/usermgr.h"
#include <string.h>
TEST_SUITE(user_crud)
TEST(create_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = user_manager_add(fixture->user_mgr, "newuser", "password",
"new@example.com", 1024 * 1024);
ASSERT_EQUAL(NWEDAV_OK, ret);
user_t user;
ret = user_manager_get_by_username(fixture->user_mgr, "newuser", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("newuser", user.username);
ASSERT_STR_EQ("new@example.com", user.email);
ASSERT_EQUAL(1024 * 1024, (int)user.quota_bytes);
fixture_destroy(fixture);
}
TEST(read_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "readuser", "pass", "read@test.com", 5000);
user_t user;
int ret = user_manager_get_by_username(fixture->user_mgr, "readuser", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("readuser", user.username);
ASSERT_STR_EQ("read@test.com", user.email);
ASSERT_EQUAL(5000, (int)user.quota_bytes);
ASSERT_EQUAL(1, user.enabled);
fixture_destroy(fixture);
}
TEST(update_user_password) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "updateuser", "oldpass", "u@test.com", 0);
int ret = user_manager_set_password(fixture->user_mgr, "updateuser", "newpass");
ASSERT_EQUAL(NWEDAV_OK, ret);
user_t user;
ret = user_manager_authenticate(fixture->user_mgr, "updateuser", "newpass", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ret = user_manager_authenticate(fixture->user_mgr, "updateuser", "oldpass", &user);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(update_user_via_modify) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "modifyuser", "pass", "old@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "modifyuser", &user);
strncpy(user.email, "new@test.com", sizeof(user.email) - 1);
int ret = user_manager_modify(fixture->user_mgr, "modifyuser", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
user_manager_get_by_username(fixture->user_mgr, "modifyuser", &user);
ASSERT_STR_EQ("new@test.com", user.email);
fixture_destroy(fixture);
}
TEST(delete_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "deleteuser", "pass", "del@test.com", 0);
int ret = user_manager_delete(fixture->user_mgr, "deleteuser");
ASSERT_EQUAL(NWEDAV_OK, ret);
user_t user;
ret = user_manager_get_by_username(fixture->user_mgr, "deleteuser", &user);
ASSERT_EQUAL(NWEDAV_ERROR_NOT_FOUND, ret);
fixture_destroy(fixture);
}
TEST(delete_nonexistent_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = user_manager_delete(fixture->user_mgr, "doesnotexist");
ASSERT_EQUAL(NWEDAV_ERROR_NOT_FOUND, ret);
fixture_destroy(fixture);
}
TEST(list_users) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "user1", "p", "u1@test.com", 0);
user_manager_add(fixture->user_mgr, "user2", "p", "u2@test.com", 0);
user_manager_add(fixture->user_mgr, "user3", "p", "u3@test.com", 0);
int count = user_manager_count(fixture->user_mgr);
ASSERT_EQUAL(3, count);
fixture_destroy(fixture);
}
TEST(enable_disable_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "toggleuser", "pass", "t@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "toggleuser", &user);
ASSERT_EQUAL(1, user.enabled);
user_manager_enable(fixture->user_mgr, "toggleuser", 0);
user_manager_get_by_username(fixture->user_mgr, "toggleuser", &user);
ASSERT_EQUAL(0, user.enabled);
user_manager_enable(fixture->user_mgr, "toggleuser", 1);
user_manager_get_by_username(fixture->user_mgr, "toggleuser", &user);
ASSERT_EQUAL(1, user.enabled);
fixture_destroy(fixture);
}
TEST(duplicate_username_fails) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "duplicate", "p1", "d1@test.com", 0);
int ret = user_manager_add(fixture->user_mgr, "duplicate", "p2", "d2@test.com", 0);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(empty_username_fails) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = user_manager_add(fixture->user_mgr, "", "pass", "e@test.com", 0);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(null_username_fails) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = user_manager_add(fixture->user_mgr, NULL, "pass", "n@test.com", 0);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
+9
View File
@@ -0,0 +1,9 @@
// retoor <retoor@molodetz.nl>
#include "test_framework.h"
int main(int argc, char **argv) {
(void)argc;
(void)argv;
return test_framework_run();
}
+122
View File
@@ -0,0 +1,122 @@
// retoor <retoor@molodetz.nl>
#include "test_framework.h"
#include <stdarg.h>
#include <time.h>
test_context_t g_test_ctx = {0};
void _test_register(const char *suite, const char *test, test_func_t func) {
test_entry_t *entry = calloc(1, sizeof(test_entry_t));
if (!entry) return;
entry->suite_name = suite;
entry->test_name = test;
entry->func = func;
if (g_test_ctx.entries_tail) {
g_test_ctx.entries_tail->next = entry;
} else {
g_test_ctx.entries = entry;
}
g_test_ctx.entries_tail = entry;
}
void _test_assert_fail(const char *file, int line, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(g_test_ctx.last_message, sizeof(g_test_ctx.last_message), fmt, args);
va_end(args);
fprintf(stderr, " FAIL: %s:%d: %s\n", file, line, g_test_ctx.last_message);
g_test_ctx.last_result = TEST_FAIL;
longjmp(g_test_ctx.jump_buffer, 1);
}
void _test_skip(const char *message) {
strncpy(g_test_ctx.last_message, message ? message : "Skipped",
sizeof(g_test_ctx.last_message) - 1);
g_test_ctx.last_result = TEST_SKIP;
longjmp(g_test_ctx.jump_buffer, 1);
}
void _test_pass(void) {
g_test_ctx.last_result = TEST_PASS;
}
int test_framework_run(void) {
printf("========================================\n");
printf(" NWEDAV TEST FRAMEWORK \n");
printf("========================================\n");
const char *current_suite = NULL;
int suite_passed = 0, suite_failed = 0, suite_skipped = 0;
test_entry_t *entry = g_test_ctx.entries;
while (entry) {
if (current_suite == NULL || strcmp(current_suite, entry->suite_name) != 0) {
if (current_suite != NULL) {
printf("----------------------------------------\n");
printf("Suite: %d passed, %d failed, %d skipped\n",
suite_passed, suite_failed, suite_skipped);
}
current_suite = entry->suite_name;
suite_passed = 0;
suite_failed = 0;
suite_skipped = 0;
printf("\n[SUITE] %s\n", current_suite);
printf("----------------------------------------\n");
}
g_test_ctx.last_result = TEST_PASS;
g_test_ctx.last_message[0] = '\0';
printf(" [TEST] %s ... ", entry->test_name);
fflush(stdout);
clock_t start = clock();
if (setjmp(g_test_ctx.jump_buffer) == 0) {
entry->func();
}
clock_t end = clock();
double elapsed = (double)(end - start) / CLOCKS_PER_SEC * 1000;
switch (g_test_ctx.last_result) {
case TEST_PASS:
printf("PASS (%.1fms)\n", elapsed);
suite_passed++;
g_test_ctx.total_passed++;
break;
case TEST_FAIL:
printf("FAIL (%.1fms)\n", elapsed);
suite_failed++;
g_test_ctx.total_failed++;
break;
case TEST_SKIP:
printf("SKIP: %s\n", g_test_ctx.last_message);
suite_skipped++;
g_test_ctx.total_skipped++;
break;
}
entry = entry->next;
}
if (current_suite != NULL) {
printf("----------------------------------------\n");
printf("Suite: %d passed, %d failed, %d skipped\n",
suite_passed, suite_failed, suite_skipped);
}
printf("\n========================================\n");
printf(" FINAL RESULTS \n");
printf("========================================\n");
printf("Total: %d passed, %d failed, %d skipped\n",
g_test_ctx.total_passed,
g_test_ctx.total_failed,
g_test_ctx.total_skipped);
printf("========================================\n");
return g_test_ctx.total_failed > 0 ? 1 : 0;
}
+162
View File
@@ -0,0 +1,162 @@
// retoor <retoor@molodetz.nl>
#ifndef TEST_FRAMEWORK_H
#define TEST_FRAMEWORK_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
typedef enum {
TEST_PASS = 0,
TEST_FAIL = 1,
TEST_SKIP = 2
} test_result_t;
typedef void (*test_func_t)(void);
typedef struct test_entry {
const char *suite_name;
const char *test_name;
test_func_t func;
struct test_entry *next;
} test_entry_t;
typedef struct {
test_entry_t *entries;
test_entry_t *entries_tail;
jmp_buf jump_buffer;
test_result_t last_result;
char last_message[1024];
int total_passed;
int total_failed;
int total_skipped;
} test_context_t;
extern test_context_t g_test_ctx;
void _test_register(const char *suite, const char *test, test_func_t func);
int test_framework_run(void);
void _test_assert_fail(const char *file, int line, const char *fmt, ...);
void _test_skip(const char *message);
void _test_pass(void);
#define TEST_CONCAT_(a, b) a##b
#define TEST_CONCAT(a, b) TEST_CONCAT_(a, b)
#define TEST_SUITE(name) \
static const char *_test_suite_name = #name;
#define TEST(name) \
static void _test_func_##name(void); \
__attribute__((constructor)) static void TEST_CONCAT(_register_, TEST_CONCAT(name, __LINE__))(void) { \
_test_register(_test_suite_name, #name, _test_func_##name); \
} \
static void _test_func_##name(void)
#define ASSERT_EQUAL(expected, actual) \
do { \
long long _exp = (long long)(expected); \
long long _act = (long long)(actual); \
if (_exp != _act) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected %lld but got %lld", _exp, _act); \
} \
} while(0)
#define ASSERT_NOT_EQUAL(expected, actual) \
do { \
long long _exp = (long long)(expected); \
long long _act = (long long)(actual); \
if (_exp == _act) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected values to differ, both are %lld", _exp); \
} \
} while(0)
#define ASSERT_STR_EQ(expected, actual) \
do { \
const char *_exp = (expected); \
const char *_act = (actual); \
if (_exp == NULL && _act == NULL) break; \
if (_exp == NULL || _act == NULL || strcmp(_exp, _act) != 0) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected \"%s\" but got \"%s\"", \
_exp ? _exp : "(null)", _act ? _act : "(null)"); \
} \
} while(0)
#define ASSERT_STR_NE(expected, actual) \
do { \
const char *_exp = (expected); \
const char *_act = (actual); \
if (_exp != NULL && _act != NULL && strcmp(_exp, _act) == 0) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected strings to differ, both are \"%s\"", _exp); \
} \
} while(0)
#define ASSERT_TRUE(condition) \
do { \
if (!(condition)) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected true but got false: %s", #condition); \
} \
} while(0)
#define ASSERT_FALSE(condition) \
do { \
if (condition) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected false but got true: %s", #condition); \
} \
} while(0)
#define ASSERT_NULL(ptr) \
do { \
if ((ptr) != NULL) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected NULL but got %p", (void*)(ptr)); \
} \
} while(0)
#define ASSERT_NOT_NULL(ptr) \
do { \
if ((ptr) == NULL) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected non-NULL but got NULL"); \
} \
} while(0)
#define ASSERT_INT_RANGE(val, min, max) \
do { \
long long _v = (long long)(val); \
long long _min = (long long)(min); \
long long _max = (long long)(max); \
if (_v < _min || _v > _max) { \
_test_assert_fail(__FILE__, __LINE__, \
"Expected %lld to be in range [%lld, %lld]", _v, _min, _max); \
} \
} while(0)
#define ASSERT_MEM_EQ(expected, actual, len) \
do { \
const void *_exp = (expected); \
const void *_act = (actual); \
size_t _len = (len); \
if (memcmp(_exp, _act, _len) != 0) { \
_test_assert_fail(__FILE__, __LINE__, \
"Memory blocks differ at length %zu", _len); \
} \
} while(0)
#define PASS() _test_pass()
#define FAIL(message) \
_test_assert_fail(__FILE__, __LINE__, "%s", message)
#define SKIP(message) \
_test_skip(message)
#endif
+142
View File
@@ -0,0 +1,142 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../../src/auth/auth.h"
#include <string.h>
TEST_SUITE(auth_basic)
TEST(base64_encode_simple) {
const char *input = "user:pass";
char *encoded = auth_base64_encode((unsigned char *)input, strlen(input));
ASSERT_NOT_NULL(encoded);
ASSERT_STR_EQ("dXNlcjpwYXNz", encoded);
free(encoded);
}
TEST(base64_encode_empty) {
char *encoded = auth_base64_encode((unsigned char *)"", 0);
ASSERT_NOT_NULL(encoded);
ASSERT_STR_EQ("", encoded);
free(encoded);
}
TEST(base64_decode_simple) {
const char *encoded = "dXNlcjpwYXNz";
size_t out_len = 0;
unsigned char *decoded = auth_base64_decode(encoded, &out_len);
ASSERT_NOT_NULL(decoded);
ASSERT_EQUAL(9, out_len);
ASSERT_MEM_EQ("user:pass", decoded, out_len);
free(decoded);
}
TEST(base64_decode_invalid) {
size_t out_len = 0;
unsigned char *decoded = auth_base64_decode("not_valid_base64!", &out_len);
ASSERT_NULL(decoded);
}
TEST(parse_basic_auth_valid) {
const char *header = "Basic dXNlcjpwYXNz";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("user", username);
ASSERT_STR_EQ("pass", password);
}
TEST(parse_basic_auth_with_leading_spaces) {
const char *header = " Basic dXNlcjpwYXNz";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("user", username);
ASSERT_STR_EQ("pass", password);
}
TEST(parse_basic_auth_missing_basic_prefix) {
const char *header = "dXNlcjpwYXNz";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_basic_auth_no_colon) {
const char *header = "Basic dXNlcm5vY29sb24=";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_basic_auth_complex_password) {
const char *header = "Basic YWRtaW46cCFAc3N3MHJkIyQlXg==";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("admin", username);
ASSERT_STR_EQ("p!@ssw0rd#$%^", password);
}
TEST(parse_basic_auth_null_header) {
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(NULL, username, sizeof(username),
password, sizeof(password));
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_basic_auth_case_insensitive) {
const char *header = "BASIC dXNlcjpwYXNz";
char username[64] = {0};
char password[256] = {0};
int ret = auth_parse_basic(header, username, sizeof(username),
password, sizeof(password));
ASSERT_EQUAL(NWEDAV_OK, ret);
}
TEST(base64_roundtrip) {
const char *original = "Test:Complex!@#Password123";
char *encoded = auth_base64_encode((unsigned char *)original, strlen(original));
ASSERT_NOT_NULL(encoded);
size_t out_len = 0;
unsigned char *decoded = auth_base64_decode(encoded, &out_len);
ASSERT_NOT_NULL(decoded);
ASSERT_EQUAL(strlen(original), out_len);
ASSERT_MEM_EQ(original, decoded, out_len);
free(encoded);
free(decoded);
}
+128
View File
@@ -0,0 +1,128 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../../src/auth/auth.h"
#include <string.h>
TEST_SUITE(auth_digest)
TEST(parse_digest_auth_valid) {
const char *header = "Digest username=\"admin\", realm=\"WebDAV\", "
"nonce=\"abc123\", uri=\"/folder\", nc=00000001, "
"cnonce=\"xyz789\", qop=auth, response=\"deadbeef\"";
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};
int ret = auth_parse_digest(header, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("admin", username);
ASSERT_STR_EQ("WebDAV", realm);
ASSERT_STR_EQ("abc123", nonce);
ASSERT_STR_EQ("/folder", uri);
ASSERT_STR_EQ("00000001", nc);
ASSERT_STR_EQ("xyz789", cnonce);
ASSERT_STR_EQ("auth", qop);
ASSERT_STR_EQ("deadbeef", response);
}
TEST(parse_digest_auth_with_spaces) {
const char *header = " Digest username=\"user\", realm=\"test\"";
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};
int ret = auth_parse_digest(header, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("user", username);
ASSERT_STR_EQ("test", realm);
}
TEST(parse_digest_auth_missing_prefix) {
const char *header = "username=\"admin\", realm=\"WebDAV\"";
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};
int ret = auth_parse_digest(header, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_digest_auth_null) {
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};
int ret = auth_parse_digest(NULL, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_digest_auth_unquoted_values) {
const char *header = "Digest username=admin, realm=test, qop=auth";
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};
int ret = auth_parse_digest(header, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("admin", username);
ASSERT_STR_EQ("test", realm);
ASSERT_STR_EQ("auth", qop);
}
TEST(parse_digest_auth_special_characters_in_uri) {
const char *header = "Digest username=\"user\", uri=\"/path/to/file%20name.txt?q=1&a=2\"";
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};
int ret = auth_parse_digest(header, username, realm, nonce, uri,
nc, cnonce, qop, response);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("/path/to/file%20name.txt?q=1&a=2", uri);
}
+160
View File
@@ -0,0 +1,160 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../../src/http/parser.h"
#include <string.h>
TEST_SUITE(http_parser)
TEST(parse_get_request_valid) {
const char *raw = "GET /index.html HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: TestClient/1.0\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_GET, req.method);
ASSERT_STR_EQ("/index.html", req.uri);
ASSERT_EQUAL(1, req.version_major);
ASSERT_EQUAL(1, req.version_minor);
ASSERT_TRUE(consumed > 0);
}
TEST(parse_put_request_with_body) {
const char *raw = "PUT /file.txt HTTP/1.1\r\n"
"Host: localhost\r\n"
"Content-Length: 13\r\n"
"\r\n"
"Hello, World!";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_PUT, req.method);
ASSERT_STR_EQ("/file.txt", req.uri);
ASSERT_EQUAL(13, req.content_length);
}
TEST(parse_propfind_request) {
const char *raw = "PROPFIND /folder/ HTTP/1.1\r\n"
"Host: localhost\r\n"
"Depth: 1\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_PROPFIND, req.method);
ASSERT_STR_EQ("/folder/", req.uri);
ASSERT_EQUAL(1, req.depth);
}
TEST(parse_copy_request_with_destination) {
const char *raw = "COPY /source.txt HTTP/1.1\r\n"
"Host: localhost\r\n"
"Destination: http://localhost/dest.txt\r\n"
"Overwrite: T\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_COPY, req.method);
ASSERT_NOT_NULL(req.destination);
ASSERT_EQUAL(1, req.overwrite);
}
TEST(parse_request_with_basic_auth) {
const char *raw = "GET /protected HTTP/1.1\r\n"
"Host: localhost\r\n"
"Authorization: Basic dXNlcjpwYXNz\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_NOT_NULL(req.authorization);
ASSERT_TRUE(strstr(req.authorization, "Basic") != NULL);
}
TEST(parse_malformed_request_no_method) {
const char *raw = "/index.html HTTP/1.1\r\n\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_incomplete_request) {
const char *raw = "GET /index.html HTTP/1.1\r\n"
"Host: local";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_request_with_lock_token) {
const char *raw = "PUT /locked.txt HTTP/1.1\r\n"
"Host: localhost\r\n"
"If: (<opaquelocktoken:abc123>)\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
}
TEST(parse_options_request) {
const char *raw = "OPTIONS * HTTP/1.1\r\n"
"Host: localhost\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_OPTIONS, req.method);
}
TEST(parse_mkcol_request) {
const char *raw = "MKCOL /newdir/ HTTP/1.1\r\n"
"Host: localhost\r\n"
"\r\n";
http_request_t req = {0};
size_t consumed = 0;
int ret = http_parse_request(raw, strlen(raw), &req, &consumed);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_METHOD_MKCOL, req.method);
ASSERT_STR_EQ("/newdir/", req.uri);
}
+177
View File
@@ -0,0 +1,177 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../../src/http/response.h"
#include "../../src/platform/memory.h"
#include <string.h>
TEST_SUITE(http_response)
TEST(response_init_defaults) {
http_response_t res;
http_response_init(&res);
ASSERT_EQUAL(HTTP_200_OK, res.status);
ASSERT_EQUAL(1, res.keep_alive);
ASSERT_EQUAL(-1, res.sendfile_fd);
ASSERT_NULL(res.body);
ASSERT_EQUAL(0, res.body_len);
}
TEST(response_set_status) {
http_response_t res;
http_response_init(&res);
http_response_set_status(&res, HTTP_404_NOT_FOUND);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, res.status);
http_response_set_status(&res, HTTP_500_INTERNAL_SERVER_ERROR);
ASSERT_EQUAL(HTTP_500_INTERNAL_SERVER_ERROR, res.status);
http_response_free(&res);
}
TEST(response_add_header) {
http_response_t res;
http_response_init(&res);
int ret = http_response_add_header(&res, "Content-Type", "text/plain");
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_NOT_NULL(res.headers);
http_response_free(&res);
}
TEST(response_add_header_duplicate_replaces) {
http_response_t res;
http_response_init(&res);
http_response_add_header(&res, "X-Custom", "value1");
http_response_add_header(&res, "X-Custom", "value2");
response_header_t *h = res.headers;
int count = 0;
while (h) {
if (strcmp(h->name, "X-Custom") == 0) {
ASSERT_STR_EQ("value2", h->value);
count++;
}
h = h->next;
}
ASSERT_EQUAL(1, count);
http_response_free(&res);
}
TEST(response_set_body) {
http_response_t res;
http_response_init(&res);
const char *body = "Hello, World!";
int ret = http_response_set_body(&res, body, strlen(body));
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_NOT_NULL(res.body);
ASSERT_EQUAL(strlen(body), res.body_len);
ASSERT_STR_EQ(body, res.body);
http_response_free(&res);
}
TEST(response_set_body_str) {
http_response_t res;
http_response_init(&res);
const char *body = "Test body content";
int ret = http_response_set_body_str(&res, body);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ(body, res.body);
http_response_free(&res);
}
TEST(response_build_complete) {
http_response_t res;
http_response_init(&res);
http_response_set_status(&res, HTTP_200_OK);
http_response_add_header(&res, "Content-Type", "text/plain");
http_response_set_body_str(&res, "OK");
buffer_t *buf = buffer_create(1024);
int ret = http_response_build(&res, buf);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_TRUE(buf->size > 0);
ASSERT_TRUE(strstr(buf->data, "HTTP/1.1 200 OK") != NULL);
ASSERT_TRUE(strstr(buf->data, "Content-Type: text/plain") != NULL);
buffer_destroy(buf);
http_response_free(&res);
}
TEST(response_error_creates_html_body) {
http_response_t res;
http_response_init(&res);
int ret = http_response_error(&res, HTTP_404_NOT_FOUND, "File not found");
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_404_NOT_FOUND, res.status);
ASSERT_NOT_NULL(res.body);
ASSERT_TRUE(strstr(res.body, "404") != NULL);
ASSERT_TRUE(strstr(res.body, "Not Found") != NULL);
http_response_free(&res);
}
TEST(response_redirect) {
http_response_t res;
http_response_init(&res);
int ret = http_response_redirect(&res, HTTP_301_MOVED_PERMANENTLY, "/new/location");
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_EQUAL(HTTP_301_MOVED_PERMANENTLY, res.status);
response_header_t *h = res.headers;
int found = 0;
while (h) {
if (strcmp(h->name, "Location") == 0) {
ASSERT_STR_EQ("/new/location", h->value);
found = 1;
}
h = h->next;
}
ASSERT_TRUE(found);
http_response_free(&res);
}
TEST(status_text_lookup) {
ASSERT_STR_EQ("OK", http_status_text(HTTP_200_OK));
ASSERT_STR_EQ("Created", http_status_text(HTTP_201_CREATED));
ASSERT_STR_EQ("Not Found", http_status_text(HTTP_404_NOT_FOUND));
ASSERT_STR_EQ("Internal Server Error", http_status_text(HTTP_500_INTERNAL_SERVER_ERROR));
ASSERT_STR_EQ("Multi-Status", http_status_text(HTTP_207_MULTI_STATUS));
ASSERT_STR_EQ("Locked", http_status_text(HTTP_423_LOCKED));
ASSERT_STR_EQ("Unknown", http_status_text(999));
}
TEST(response_build_headers_only) {
http_response_t res;
http_response_init(&res);
http_response_set_status(&res, HTTP_200_OK);
http_response_add_header(&res, "Content-Length", "1000");
buffer_t *buf = buffer_create(1024);
int ret = http_response_build_headers_only(&res, buf);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_TRUE(strstr(buf->data, "HTTP/1.1 200 OK") != NULL);
ASSERT_TRUE(strstr(buf->data, "\r\n\r\n") != NULL);
buffer_destroy(buf);
http_response_free(&res);
}
+216
View File
@@ -0,0 +1,216 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../fixtures/fixtures.h"
#include "../../src/storage/locks.h"
#include <string.h>
TEST_SUITE(lock_manager)
TEST(create_lock_manager) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
ASSERT_NOT_NULL(fixture->locks);
fixture_destroy(fixture);
}
TEST(create_exclusive_lock) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/testfile.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.depth = LOCK_DEPTH_0;
lock.timeout = 3600;
int ret = lock_manager_create_lock(fixture->locks, &lock);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_TRUE(strlen(lock.token) > 0);
fixture_destroy(fixture);
}
TEST(create_shared_lock) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/shared.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_SHARED;
lock.type = LOCK_TYPE_WRITE;
lock.depth = LOCK_DEPTH_0;
lock.timeout = 3600;
int ret = lock_manager_create_lock(fixture->locks, &lock);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_TRUE(strlen(lock.token) > 0);
fixture_destroy(fixture);
}
TEST(remove_lock) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/locked.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_create_lock(fixture->locks, &lock);
int ret = lock_manager_remove_lock(fixture->locks, lock.token);
ASSERT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(check_lock_exists) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int is_locked = lock_manager_is_locked(fixture->locks, "/unlocked.txt");
ASSERT_FALSE(is_locked);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/nowlocked.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_create_lock(fixture->locks, &lock);
is_locked = lock_manager_is_locked(fixture->locks, "/nowlocked.txt");
ASSERT_TRUE(is_locked);
fixture_destroy(fixture);
}
TEST(get_lock_by_token) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/info.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "testowner", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.depth = LOCK_DEPTH_0;
lock.timeout = 7200;
lock_manager_create_lock(fixture->locks, &lock);
lock_entry_t retrieved;
int ret = lock_manager_get_lock(fixture->locks, lock.token, &retrieved);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("testowner", retrieved.owner);
ASSERT_EQUAL(LOCK_SCOPE_EXCLUSIVE, retrieved.scope);
ASSERT_EQUAL(LOCK_TYPE_WRITE, retrieved.type);
fixture_destroy(fixture);
}
TEST(refresh_lock) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/refresh.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 100;
lock_manager_create_lock(fixture->locks, &lock);
int ret = lock_manager_refresh_lock(fixture->locks, lock.token, 7200);
ASSERT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(get_locks_for_path) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/pathlocks.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock_manager_create_lock(fixture->locks, &lock);
lock_entry_t *locks = NULL;
int count = 0;
int ret = lock_manager_get_locks_for_path(fixture->locks, "/pathlocks.txt", &locks, &count);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_TRUE(count >= 1);
if (locks) free(locks);
fixture_destroy(fixture);
}
TEST(check_lock_with_token) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
lock_entry_t lock;
memset(&lock, 0, sizeof(lock));
strncpy(lock.resource_path, "/check.txt", sizeof(lock.resource_path) - 1);
strncpy(lock.owner, "user1", sizeof(lock.owner) - 1);
lock.scope = LOCK_SCOPE_EXCLUSIVE;
lock.type = LOCK_TYPE_WRITE;
lock.timeout = 3600;
lock.user_id = 1;
lock_manager_create_lock(fixture->locks, &lock);
int ret = lock_manager_check_lock(fixture->locks, "/check.txt", lock.token, 1);
ASSERT_EQUAL(NWEDAV_OK, ret);
ret = lock_manager_check_lock(fixture->locks, "/check.txt", "wrong-token", 2);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(cleanup_expired_locks) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = lock_manager_cleanup_expired(fixture->locks);
ASSERT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(generate_unique_tokens) {
char *token1 = lock_generate_token();
char *token2 = lock_generate_token();
ASSERT_NOT_NULL(token1);
ASSERT_NOT_NULL(token2);
ASSERT_TRUE(strcmp(token1, token2) != 0);
free(token1);
free(token2);
}
+142
View File
@@ -0,0 +1,142 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../fixtures/fixtures.h"
#include "../../src/storage/backend.h"
#include <string.h>
TEST_SUITE(storage_path)
TEST(path_traversal_blocked_dotdot) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int exists = storage_exists(fixture->storage, "/../etc/passwd");
ASSERT_FALSE(exists);
exists = storage_exists(fixture->storage, "/folder/../../etc/passwd");
ASSERT_FALSE(exists);
fixture_destroy(fixture);
}
TEST(path_traversal_blocked_encoded) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int exists = storage_exists(fixture->storage, "/%2e%2e/etc/passwd");
ASSERT_FALSE(exists);
fixture_destroy(fixture);
}
TEST(valid_path_normalized) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int ret = storage_create_collection(fixture->storage, "/testdir");
ASSERT_EQUAL(NWEDAV_OK, ret);
int exists = storage_exists(fixture->storage, "/testdir");
ASSERT_TRUE(exists);
exists = storage_exists(fixture->storage, "/testdir/");
ASSERT_TRUE(exists);
fixture_destroy(fixture);
}
TEST(create_nested_path) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int ret = storage_create_collection(fixture->storage, "/parent");
ASSERT_EQUAL(NWEDAV_OK, ret);
ret = storage_create_collection(fixture->storage, "/parent/child");
ASSERT_EQUAL(NWEDAV_OK, ret);
int exists = storage_exists(fixture->storage, "/parent/child");
ASSERT_TRUE(exists);
fixture_destroy(fixture);
}
TEST(create_collection_parent_missing) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int ret = storage_create_collection(fixture->storage, "/missing/child");
ASSERT_EQUAL(NWEDAV_ERROR_CONFLICT, ret);
fixture_destroy(fixture);
}
TEST(write_file_creates_in_collection) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int ret = storage_create_collection(fixture->storage, "/folder");
ASSERT_EQUAL(NWEDAV_OK, ret);
const char *content = "test content";
ret = storage_write_file(fixture->storage, "/folder/test.txt",
content, strlen(content));
ASSERT_EQUAL(NWEDAV_OK, ret);
int exists = storage_exists(fixture->storage, "/folder/test.txt");
ASSERT_TRUE(exists);
fixture_destroy(fixture);
}
TEST(write_file_parent_missing) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
const char *content = "test";
int ret = storage_write_file(fixture->storage, "/missing/file.txt",
content, strlen(content));
ASSERT_EQUAL(NWEDAV_ERROR_CONFLICT, ret);
fixture_destroy(fixture);
}
TEST(delete_empty_collection) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
storage_create_collection(fixture->storage, "/todelete");
int ret = storage_delete(fixture->storage, "/todelete");
ASSERT_EQUAL(NWEDAV_OK, ret);
int exists = storage_exists(fixture->storage, "/todelete");
ASSERT_FALSE(exists);
fixture_destroy(fixture);
}
TEST(delete_file) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
storage_write_file(fixture->storage, "/file.txt", "data", 4);
int ret = storage_delete(fixture->storage, "/file.txt");
ASSERT_EQUAL(NWEDAV_OK, ret);
int exists = storage_exists(fixture->storage, "/file.txt");
ASSERT_FALSE(exists);
fixture_destroy(fixture);
}
TEST(root_path_always_exists) {
test_fixture_t *fixture = fixture_create_with_storage();
ASSERT_NOT_NULL(fixture);
int exists = storage_exists(fixture->storage, "/");
ASSERT_TRUE(exists);
fixture_destroy(fixture);
}
+184
View File
@@ -0,0 +1,184 @@
// retoor <retoor@molodetz.nl>
#include "../test_framework.h"
#include "../fixtures/fixtures.h"
#include "../../src/user/usermgr.h"
#include <string.h>
TEST_SUITE(user_database)
TEST(create_user_manager) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
ASSERT_NOT_NULL(fixture->user_mgr);
fixture_destroy(fixture);
}
TEST(add_user_success) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
int ret = user_manager_add(fixture->user_mgr, "testuser", "password123",
"test@example.com", 10 * 1024 * 1024);
ASSERT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(add_user_duplicate_fails) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "alice", "pass1", "alice@test.com", 0);
int ret = user_manager_add(fixture->user_mgr, "alice", "pass2", "alice2@test.com", 0);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(get_user_by_username) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "bob", "secret", "bob@test.com", 5000000);
user_t user;
int ret = user_manager_get_by_username(fixture->user_mgr, "bob", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("bob", user.username);
ASSERT_STR_EQ("bob@test.com", user.email);
ASSERT_EQUAL(5000000, user.quota_bytes);
ASSERT_EQUAL(1, user.enabled);
fixture_destroy(fixture);
}
TEST(get_user_not_found) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_t user;
int ret = user_manager_get_by_username(fixture->user_mgr, "nonexistent", &user);
ASSERT_EQUAL(NWEDAV_ERROR_NOT_FOUND, ret);
fixture_destroy(fixture);
}
TEST(authenticate_valid) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "charlie", "mypassword", "charlie@test.com", 0);
user_t user;
int ret = user_manager_authenticate(fixture->user_mgr, "charlie", "mypassword", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ASSERT_STR_EQ("charlie", user.username);
fixture_destroy(fixture);
}
TEST(authenticate_wrong_password) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "dave", "correct", "dave@test.com", 0);
user_t user;
int ret = user_manager_authenticate(fixture->user_mgr, "dave", "wrong", &user);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(authenticate_disabled_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "eve", "pass", "eve@test.com", 0);
user_manager_enable(fixture->user_mgr, "eve", 0);
user_t user;
int ret = user_manager_authenticate(fixture->user_mgr, "eve", "pass", &user);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(delete_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "frank", "pass", "frank@test.com", 0);
int ret = user_manager_delete(fixture->user_mgr, "frank");
ASSERT_EQUAL(NWEDAV_OK, ret);
user_t user;
ret = user_manager_get_by_username(fixture->user_mgr, "frank", &user);
ASSERT_EQUAL(NWEDAV_ERROR_NOT_FOUND, ret);
fixture_destroy(fixture);
}
TEST(set_password) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "grace", "oldpass", "grace@test.com", 0);
user_manager_set_password(fixture->user_mgr, "grace", "newpass");
user_t user;
int ret = user_manager_authenticate(fixture->user_mgr, "grace", "newpass", &user);
ASSERT_EQUAL(NWEDAV_OK, ret);
ret = user_manager_authenticate(fixture->user_mgr, "grace", "oldpass", &user);
ASSERT_NOT_EQUAL(NWEDAV_OK, ret);
fixture_destroy(fixture);
}
TEST(user_count) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
ASSERT_EQUAL(0, user_manager_count(fixture->user_mgr));
user_manager_add(fixture->user_mgr, "user1", "p", "u1@test.com", 0);
ASSERT_EQUAL(1, user_manager_count(fixture->user_mgr));
user_manager_add(fixture->user_mgr, "user2", "p", "u2@test.com", 0);
ASSERT_EQUAL(2, user_manager_count(fixture->user_mgr));
user_manager_delete(fixture->user_mgr, "user1");
ASSERT_EQUAL(1, user_manager_count(fixture->user_mgr));
fixture_destroy(fixture);
}
TEST(enable_disable_user) {
test_fixture_t *fixture = fixture_create();
ASSERT_NOT_NULL(fixture);
user_manager_add(fixture->user_mgr, "henry", "pass", "h@test.com", 0);
user_t user;
user_manager_get_by_username(fixture->user_mgr, "henry", &user);
ASSERT_EQUAL(1, user.enabled);
user_manager_enable(fixture->user_mgr, "henry", 0);
user_manager_get_by_username(fixture->user_mgr, "henry", &user);
ASSERT_EQUAL(0, user.enabled);
user_manager_enable(fixture->user_mgr, "henry", 1);
user_manager_get_by_username(fixture->user_mgr, "henry", &user);
ASSERT_EQUAL(1, user.enabled);
fixture_destroy(fixture);
}