Tue, 25 Aug 2020 12:07:56 +0200
merge branch config into webdav
--- a/configure Mon Aug 24 19:19:56 2020 +0200 +++ b/configure Tue Aug 25 12:07:56 2020 +0200 @@ -57,7 +57,7 @@ --mandir=DIR man documentation [DATAROOTDIR/man] Optional Features: - --enable-pg + --enable-postgresql __EOF__ } @@ -96,10 +96,10 @@ elif [ $ARG = "--help" ]; then printhelp exit 0 - elif [[ $ARG == --enable-pg ]]; then - FEATURE_PG=on - elif [[ $ARG == --disable-pg ]]; then - unset FEATURE_PG + elif [[ $ARG == --enable-postgresql ]]; then + FEATURE_POSTGRESQL=on + elif [[ $ARG == --disable-postgresql ]]; then + unset FEATURE_POSTGRESQL fi done @@ -265,6 +265,7 @@ fi CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libpq`" LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libpq`" + CFLAGS="$CFLAGS -DENABLE_POSTGRESQL" echo yes return 0 done @@ -469,6 +470,19 @@ ERROR=1 fi +# Features +if [ ! -z "$FEATURE_POSTGRESQL" ]; then + # check dependency + dependency_libpq + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $FEATURE_POSTGRESQL != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libpq " + ERROR=1 + fi + fi +fi + echo >> $TEMP_DIR/config.mk if [ ! -z "${CFLAGS}" ]; then
--- a/make/configure.vm Mon Aug 24 19:19:56 2020 +0200 +++ b/make/configure.vm Tue Aug 25 12:07:56 2020 +0200 @@ -534,6 +534,23 @@ fi #end +# Features +#foreach( $feature in $target.features ) +if [ ! -z "$${feature.getVarName()}" ]; then +#foreach( $dependency in $feature.dependencies ) + # check dependency + dependency_$dependency + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $${feature.getVarName()} != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 + fi + fi +#end +fi +#end + #foreach( $opt in $target.options ) # Option: --${opt.argument} if [ -z ${D}${opt.getVarName()} ]; then
--- a/make/project.xml Mon Aug 24 19:19:56 2020 +0200 +++ b/make/project.xml Tue Aug 25 12:07:56 2020 +0200 @@ -76,10 +76,11 @@ <!-- optional dependencies --> <dependency name="libpq"> <pkgconfig>libpq</pkgconfig> + <cflags>-DENABLE_POSTGRESQL</cflags> </dependency> <target> - <feature name="pg" default="false"> + <feature name="postgresql" default="false"> <dependencies>libpq</dependencies> </feature> <dependencies>libxml2,openssl</dependencies>
--- a/make/suncc.mk Mon Aug 24 19:19:56 2020 +0200 +++ b/make/suncc.mk Tue Aug 25 12:07:56 2020 +0200 @@ -2,9 +2,9 @@ # suncc toolchain # -CFLAGS += -xc99 -g -LDFLAGS += -Wl,-R,'$$ORIGIN/../lib' +CFLAGS += -xc99 -g -m64 +LDFLAGS += -m64 -Wl,-R,'$$ORIGIN/../lib' SHLIB_CFLAGS = -Kpic -SHLIB_LDFLAGS = -G +SHLIB_LDFLAGS = -G -m64
--- a/src/server/daemon/acl.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/acl.c Tue Aug 25 12:07:56 2020 +0200 @@ -100,6 +100,7 @@ } User* acllist_getuser(Session *sn, Request *rq, ACLListHandle *list) { + // TODO: cache result if(!sn || !rq || !list) { return NULL; } @@ -316,7 +317,7 @@ uid_t owner, gid_t owninggroup); -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) { +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) { sstr_t p; if(path[0] != '/') { size_t n = 128; @@ -331,11 +332,11 @@ } } sstr_t wd = sstr(cwd); - sstr_t pp = sstr(path); + sstr_t pp = sstr((char*)path); p = sstrcat(3, wd, sstrn("/", 1), pp); } else { - p = sstrdup(sstr(path)); + p = sstrdup(sstr((char*)path)); } if(p.ptr[p.length-1] == '/') { p.ptr[p.length-1] = 0; @@ -461,6 +462,11 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + // TODO: + return 1; +} + int solaris_acl_check( char *path, struct stat *s, @@ -571,6 +577,10 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + return 1; +} + void fs_acl_finish() { } @@ -583,6 +593,10 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + return 1; +} + void fs_acl_finish() { } @@ -638,6 +652,11 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + // TODO + return 1; +} + void fs_acl_finish() { struct passwd *pw = conf_getglobals()->Vuserpw; if(!pw) {
--- a/src/server/daemon/acl.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/acl.h Tue Aug 25 12:07:56 2020 +0200 @@ -48,7 +48,8 @@ // file system acl functions -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask); +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask); +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask); void fs_acl_finish(); #ifdef __cplusplus
--- a/src/server/daemon/httprequest.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/httprequest.c Tue Aug 25 12:07:56 2020 +0200 @@ -87,6 +87,46 @@ return S("/"); } +NSAPISession* nsapisession_create(pool_handle_t *pool) { + NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); + if(!sn) { + return NULL; + } + + ZERO(sn, sizeof(NSAPISession)); + + sn->sn.pool = pool; + sn->allocator = util_pool_allocator(pool); + + sn->sn.client = pblock_create_pool(sn->sn.pool, 8); + if(!sn->sn.client) { + pool_free(pool, sn); + return NULL; + } + sn->sn.fill = 1; + + return sn; +} + +int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io) { + SessionHandler *sh = conn->session_handler; + WSBool ssl; + IOStream *sio = sh->create_iostream(sh, conn, sn->sn.pool, &ssl); + if(!sio) { + return 1; + } + *io = sio; + IOStream *http = httpstream_new(sn->sn.pool, sio); + if(!http) { + return 1; + } + sn->connection = conn; + sn->netbuf = inbuf; + sn->sn.csd = http; + sn->sn.ssl = ssl; + return 0; +} + int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) { // handle nsapi request @@ -94,11 +134,11 @@ pool_handle_t *pool = pool_create(); // create nsapi data structures - NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); + NSAPISession *sn = nsapisession_create(pool); if(sn == NULL) { /* TODO: error */ } - ZERO(sn, sizeof(NSAPISession)); + NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); if(rq == NULL) { /* TODO: error */ @@ -108,25 +148,16 @@ rq->phase = NSAPIAuthTrans; // fill session structure - sn->connection = request->connection; - sn->netbuf = request->netbuf; - sn->sn.pool = pool; - SessionHandler *sh = request->connection->session_handler; - WSBool ssl; - IOStream *io = sh->create_iostream(sh, request->connection, pool, &ssl); - sn->sn.csd = httpstream_new(pool, io); - sn->sn.ssl = ssl; - - sn->sn.client = pblock_create_pool(sn->sn.pool, 8); - sn->sn.next = NULL; - sn->sn.fill = 1; - sn->sn.subject = NULL; + IOStream *io = NULL; + if(nsapisession_setconnection(sn, request->connection, request->netbuf, &io)) { + // TODO: error + } if(!ev) { ev = ev_instance(get_default_event_handler()); } sn->sn.ev = ev; - + // the session needs the current server configuration sn->config = request->connection->listener->cfg; @@ -342,7 +373,7 @@ // check for request body and prepare input buffer char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers); if(ctlen_str) { - int ctlen = atoi(ctlen_str); + int ctlen = atoi(ctlen_str); // TODO: use other func //printf("request body length: %d\n", ctlen); @@ -914,7 +945,9 @@ if(ret != REQ_PROCEED) { // default error handler - nsapi_error_request((Session*)sn, (Request*)rq); + if(!rq->rq.senthdrs) { + nsapi_error_request((Session*)sn, (Request*)rq); + } } return ret;
--- a/src/server/daemon/httprequest.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/httprequest.h Tue Aug 25 12:07:56 2020 +0200 @@ -73,6 +73,10 @@ sstr_t http_request_get_abspath(HTTPRequest *req); + +NSAPISession* nsapisession_create(pool_handle_t *pool); +int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io); + /* * starts request processing after reading the request header *
--- a/src/server/daemon/protocol.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/protocol.c Tue Aug 25 12:07:56 2020 +0200 @@ -347,7 +347,7 @@ } // set stream property - HttpStream *stream = (HttpStream*)sn->csd; + HttpStream *stream = (HttpStream*)sn->csd; // TODO: make this typesafe stream->chunked_enc = 1; rq->rq_attr.chunked = 1; } @@ -377,6 +377,16 @@ return 0; } +int http_send_continue(Session *sn) { + NSAPISession *s = (NSAPISession*)sn; + sstr_t msg = S("HTTP/1.1 100 Continue\r\n\r\n"); + int w = s->connection->write(s->connection, msg.ptr, msg.length); + if(w != msg.length) { + return 1; + } + return 0; +} + int request_header(char *name, char **value, Session *sn, Request *rq) { const pb_key *key = pblock_key(name); pb_param *pp = pblock_findkey(key, rq->headers);
--- a/src/server/daemon/protocol.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/protocol.h Tue Aug 25 12:07:56 2020 +0200 @@ -46,6 +46,8 @@ int http_start_response(Session *sn, Request *rq); +int http_send_continue(Session *sn); + int request_header(char *name, char **value, Session *sn, Request *rq); void http_get_scheme_host_port(
--- a/src/server/daemon/session.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/session.c Tue Aug 25 12:07:56 2020 +0200 @@ -40,3 +40,8 @@ NSAPISession *sn = (NSAPISession*)s; return sn->config; } + +NSAPI_PUBLIC void* session_get_allocator(Session *sn) { + return &((NSAPISession*)sn)->allocator; +} +
--- a/src/server/daemon/session.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/session.h Tue Aug 25 12:07:56 2020 +0200 @@ -47,6 +47,8 @@ threadpool_t *currentpool; threadpool_t *defaultpool; + UcxAllocator allocator; + ServerConfiguration *config; }; @@ -57,6 +59,8 @@ // get the server configuration of this session NSAPI_PUBLIC void* session_get_config(Session *s); +NSAPI_PUBLIC void* session_get_allocator(Session *sn); + #ifdef __cplusplus } #endif
--- a/src/server/daemon/vfs.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/vfs.c Tue Aug 25 12:07:56 2020 +0200 @@ -52,9 +52,12 @@ sys_vfs_stat, sys_vfs_fstat, sys_vfs_opendir, + sys_vfs_fdopendir, sys_vfs_mkdir, sys_vfs_unlink, - VFS_CHECKS_ACL + sys_vfs_rmdir, + VFS_CHECKS_ACL, + NULL }; static VFS_IO sys_file_io = { @@ -97,6 +100,9 @@ WS_ASSERT(rq); VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext)); + if(!ctx) { + return NULL; + } ctx->sn = sn; ctx->rq = rq; ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs; @@ -108,7 +114,7 @@ return ctx; } -SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) { +SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -129,19 +135,19 @@ return file; } -SYS_FILE vfs_openRO(VFSContext *ctx, char *path) { +SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_RDONLY); } -SYS_FILE vfs_openWO(VFSContext *ctx, char *path) { +SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_WRONLY | O_CREAT); } -SYS_FILE vfs_openRW(VFSContext *ctx, char *path) { +SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT); } -int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { +int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -181,7 +187,7 @@ } } -VFS_DIR vfs_opendir(VFSContext *ctx, char *path) { +VFS_DIR vfs_opendir(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -202,6 +208,27 @@ return dir; } +VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + WS_ASSERT(ctx); + WS_ASSERT(path); + + uint32_t access_mask = ctx->aclreqaccess | ACL_LIST; + + // ctx->aclreqaccess should be the complete access mask + uint32_t m = ctx->aclreqaccess; // save original access mask + ctx->aclreqaccess = access_mask; // set mask for vfs->open call + if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) { + // VFS does not evaluates the ACL itself, so we have to do it here + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { + return NULL; + } + } + VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd); + ctx->aclreqaccess = m; // restore original access mask + return dir; +} + int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) { WS_ASSERT(dir); WS_ASSERT(entry); @@ -227,23 +254,29 @@ } } -int vfs_mkdir(VFSContext *ctx, char *path) { +int vfs_mkdir(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE); } -int vfs_unlink(VFSContext *ctx, char *path) { +int vfs_unlink(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE); } +int vfs_rmdir(VFSContext *ctx, const char *path) { + WS_ASSERT(ctx); + WS_ASSERT(path); + + return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE); +} // private -int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) { +int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) { uint32_t access_mask = ctx->aclreqaccess; access_mask |= access; @@ -264,7 +297,7 @@ /* system vfs implementation */ -SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags) { +SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) { uint32_t access_mask = ctx->aclreqaccess; pool_handle_t *pool = ctx->pool; @@ -313,7 +346,7 @@ return file; } -int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { +int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { uint32_t access_mask = ctx->aclreqaccess; // check ACLs @@ -353,7 +386,7 @@ return 0; } -VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path) { +VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path) { uint32_t access_mask = ctx->aclreqaccess; pool_handle_t *pool = ctx->pool; @@ -422,16 +455,76 @@ return dir; } -int sys_vfs_mkdir(VFSContext *ctx, char *path) { +VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + uint32_t access_mask = ctx->aclreqaccess; + pool_handle_t *pool = ctx->pool; + + // check ACLs + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { + return NULL; + } + + if(sysacl.acl) { + if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return NULL; + } + } + + // open directory + DIR *sys_dir = fdopendir(fd->fd); + if(!sys_dir) { + if(ctx) { + ctx->vfs_errno = errno; + sys_set_error_status(ctx); + } + return NULL; + } + + SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir)); + if(!dir_data) { + closedir(sys_dir); + return NULL; + } + long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX); + size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1; + dir_data->cur = VFS_MALLOC(pool, entry_len); + if(!dir_data->cur) { + closedir(sys_dir); + VFS_FREE(pool, dir_data); + return NULL; + } + dir_data->dir = sys_dir; + + VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir)); + if(!dir) { + closedir(sys_dir); + VFS_FREE(pool, dir_data->cur); + VFS_FREE(pool, dir_data); + return NULL; + } + dir->ctx = ctx; + dir->data = dir_data; + dir->fd = fd->fd; + dir->io = &sys_dir_io; + return dir; +} + +int sys_vfs_mkdir(VFSContext *ctx, const char *path) { return sys_path_op(ctx, path, sys_mkdir); } -int sys_vfs_unlink(VFSContext *ctx, char *path) { +int sys_vfs_unlink(VFSContext *ctx, const char *path) { return sys_path_op(ctx, path, sys_unlink); } +int sys_vfs_rmdir(VFSContext *ctx, const char *path) { + return sys_path_op(ctx, path, sys_rmdir); +} -int sys_path_op(VFSContext *ctx, char *path, sys_op_f op) { + +int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) { uint32_t access_mask = ctx->aclreqaccess; // check ACLs @@ -539,6 +632,7 @@ entry->name = name; if(getstat) { // TODO: check ACLs again for new path + entry->stat_errno = 0; if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) { entry->stat_errno = errno; } @@ -567,7 +661,7 @@ } } -int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) { +int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) { mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; int ret = mkdir(path, mode); if(ret == 0) { @@ -580,10 +674,14 @@ return ret; } -int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) { +int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) { return unlink(path); } +int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) { + return rmdir(path); +} + /* public file api */ NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) {
--- a/src/server/daemon/vfs.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/vfs.h Tue Aug 25 12:07:56 2020 +0200 @@ -49,18 +49,20 @@ int vfs_init(); -typedef int(*vfs_op_f)(VFSContext *, char *); -typedef int(*sys_op_f)(VFSContext *, char *, SysACL *); -int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access); +typedef int(*vfs_op_f)(VFSContext *, const char *); +typedef int(*sys_op_f)(VFSContext *, const char *, SysACL *); +int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access); -SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags); -int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf); +SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags); +int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf); int sys_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf); -VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path); -int sys_vfs_mkdir(VFSContext *ctx, char *path); -int sys_vfs_unlink(VFSContext *ctx, char *path); +VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path); +VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd); +int sys_vfs_mkdir(VFSContext *ctx, const char *path); +int sys_vfs_unlink(VFSContext *ctx, const char *path); +int sys_vfs_rmdir(VFSContext *ctx, const char *path); -int sys_path_op(VFSContext *ctx, char *path, sys_op_f op); +int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op); int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *externacl); void sys_set_error_status(VFSContext *ctx); @@ -76,8 +78,9 @@ int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat); void sys_dir_close(VFS_DIR dir); -int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl); -int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl); +int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl); +int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl); +int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl); void vfs_queue_aio(aiocb_s *aiocb, VFSAioOp op);
--- a/src/server/daemon/ws-fn.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/daemon/ws-fn.c Tue Aug 25 12:07:56 2020 +0200 @@ -70,5 +70,7 @@ { "set-variable", set_variable, NULL, NULL, 0}, { "common-log", common_log, NULL, NULL, 0}, { "send-cgi", send_cgi, NULL, NULL, 0}, + { "webdav-init", webdav_init, NULL, NULL, 0}, + { "webdav-service", webdav_service, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, 0} };
--- a/src/server/public/acl.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/public/acl.h Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met:
--- a/src/server/public/auth.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/public/auth.h Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met:
--- a/src/server/public/nsapi.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/public/nsapi.h Tue Aug 25 12:07:56 2020 +0200 @@ -110,6 +110,9 @@ /* --- Begin miscellaneous definitions --- */ +#define WS_TRUE 1 +#define WS_FALSE 0 + /* Used in some places as a length limit on error messages */ #define MAGNUS_ERROR_LEN 1024 @@ -404,6 +407,7 @@ // they are VFSFile* // TODO: fix NOTE +typedef int WSBool; #ifndef SYS_FILE_T typedef struct VFSFile *SYS_FILE;
--- a/src/server/public/vfs.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/public/vfs.h Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -48,13 +48,16 @@ #define VFS_ENTRY VFSEntry struct VFS { - SYS_FILE (*open)(VFSContext *ctx, char *path, int oflags); - int (*stat)(VFSContext *ctx, char *path, struct stat *buf); + SYS_FILE (*open)(VFSContext *ctx, const char *path, int oflags); + int (*stat)(VFSContext *ctx, const char *path, struct stat *buf); int (*fstat)(VFSContext *ctx, SYS_FILE fd, struct stat *buf); - VFS_DIR (*opendir)(VFSContext *ctx, char *path); - int (*mkdir)(VFSContext *ctx, char *path); - int (*unlink)(VFSContext *ctx, char *path); + VFS_DIR (*opendir)(VFSContext *ctx, const char *path); + VFS_DIR (*fdopendir)(VFSContext *ctx, SYS_FILE fd); + int (*mkdir)(VFSContext *ctx, const char *path); + int (*unlink)(VFSContext *ctx, const char *path); + int (*rmdir)(VFSContext *Ctx, const char *path); uint32_t flags; + void *instance; }; struct VFSContext { @@ -70,16 +73,16 @@ struct VFSFile { VFSContext *ctx; - VFS_IO *io; // IO functions - void *data; // private data used by the VFSFile implementation - int fd; // native file descriptor if available, or -1 + VFS_IO *io; /* IO functions */ + void *data; /* private data used by the VFSFile implementation */ + int fd; /* native file descriptor if available, or -1 */ }; struct VFSDir { VFSContext *ctx; VFS_DIRIO *io; - void *data; // private data used by the VFSDir implementation - int fd; // native file descriptor if available, or -1 + void *data; /* private data used by the VFSDir implementation */ + int fd; /* native file descriptor if available, or -1 */ }; struct VFSEntry { @@ -116,19 +119,21 @@ */ VFSContext* vfs_request_context(Session *sn, Request *rq); -SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags); -SYS_FILE vfs_openRO(VFSContext *ctx, char *path); -SYS_FILE vfs_openWO(VFSContext *ctx, char *path); -SYS_FILE vfs_openRW(VFSContext *ctx, char *path); -int vfs_stat(VFSContext *ctx, char *path, struct stat *buf); +SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags); +SYS_FILE vfs_openRO(VFSContext *ctx, const char *path); +SYS_FILE vfs_openWO(VFSContext *ctx, const char *path); +SYS_FILE vfs_openRW(VFSContext *ctx, const char *path); +int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf); int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf); void vfs_close(SYS_FILE fd); -VFS_DIR vfs_opendir(VFSContext *ctx, char *path); +VFS_DIR vfs_opendir(VFSContext *ctx, const char *path); +VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd); int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry); int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry); void vfs_closedir(VFS_DIR dir); -int vfs_mkdir(VFSContext *ctx, char *path); -int vfs_unlink(VFSContext *ctx, char *path); +int vfs_mkdir(VFSContext *ctx, const char *path); +int vfs_unlink(VFSContext *ctx, const char *path); +int vfs_rmdir(VFSContext *ctx, const char *path); #ifdef __cplusplus }
--- a/src/server/public/webdav.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/public/webdav.h Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2020 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,6 +30,7 @@ #define WS_WEBDAV_H #include "nsapi.h" +#include "vfs.h" #include <sys/file.h> #include <sys/stat.h> @@ -39,7 +40,408 @@ extern "C" { #endif +typedef struct WebdavBackend WebdavBackend; + +typedef struct WebdavProperty WebdavProperty; +typedef struct WebdavPList WebdavPList; +typedef struct WebdavNSList WebdavNSList; +typedef struct WebdavPListIterator WebdavPListIterator; + +typedef enum WebdavLockScope WebdavLockScope; +typedef enum WebdavLockType WebdavLockType; + +typedef enum WebdavValueType WebdavValueType; + +typedef struct WebdavPropfindRequest WebdavPropfindRequest; +typedef struct WebdavProppatchRequest WebdavProppatchRequest; +typedef struct WebdavLockRequest WebdavLockRequest; + +typedef struct WebdavVFSRequest WebdavVFSRequest; + +typedef struct WebdavResponse WebdavResponse; +typedef struct WebdavResource WebdavResource; + +typedef struct WebdavVFSProperties WebdavVFSProperties; + +typedef struct WebdavOperation WebdavOperation; + +typedef struct WSXmlData WSXmlData; +typedef struct WSText WSText; + +typedef struct _xmlNs WSNamespace; +typedef struct _xmlNode WSXmlNode; + +#define WS_NODE_ELEMENT 1 +#define WS_NODE_TEXT 3 +#define WS_NODE_CDATA 4 +#define WS_NODE_ENTITY_REF 5 + +typedef int(*wsxml_func)(WSXmlNode *, void *); + +/* propfind settings */ + +/* + * Use the vfs to stat files or read the directory children + */ +#define WS_WEBDAV_PROPFIND_USE_VFS 0x01 + +/* + * Use the vfs to open a file for proppatch + */ +#define WS_WEBDAV_PROPPATCH_USE_VFS 0x02 + + +enum WebdavValueType { + WS_VALUE_NO_TYPE = 0, + WS_VALUE_XML_NODE, + WS_VALUE_XML_DATA, + WS_VALUE_TEXT +}; + +struct WSText { + char *str; + size_t length; +}; + +struct WebdavProperty { + WSNamespace *namespace; + + const char *name; + + char *lang; + + union { + WSXmlNode *node; + WSXmlData *data; + WSText text; + } value; + WebdavValueType vtype; +}; + +struct WSXmlData { + WebdavNSList *namespaces; + char *data; + size_t length; +}; + +struct WebdavPList { + WebdavProperty *property; + WebdavPList *prev; + WebdavPList *next; +}; + +struct WebdavNSList { + WSNamespace *namespace; + WebdavNSList *prev; + WebdavNSList *next; +}; + +struct WebdavPListIterator { + WebdavPList **list; + WebdavPList *cur; + WebdavPList *next; + size_t index; +}; + +enum WebdavLockScope { + WEBDAV_LOCK_EXCLUSIVE = 0, + WEBDAV_LOCK_SHARED, + WEBDAV_LOCK_SCOPE_UNKNOWN +}; + +enum WebdavLockType { + WEBDAV_LOCK_WRITE = 0, + WEBDAV_LOCK_TYPE_UNKNOWN +}; + +struct WebdavPropfindRequest { + Session *sn; + Request *rq; + + void *doc; + + /* + * list of requested properties + */ + WebdavPList *properties; + + /* + * number of properties + */ + size_t propcount; + + WSBool allprop; + WSBool propname; + WSBool deadproperties; + + int depth; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavProppatchRequest { + Session *sn; + Request *rq; + + void *doc; + + WebdavPList *set; + size_t setcount; + + WebdavPList *remove; + size_t removecount; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavVFSRequest { + Session *sn; + Request *rq; + + char *path; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavLockRequest { + Session *sn; + Request *rq; + + void *doc; + + WebdavLockScope scope; + WebdavLockType type; + + WSXmlNode *owner; +}; + +struct WebdavVFSProperties { + uint32_t getcontentlength:1; + uint32_t getlastmodified:1; + uint32_t getresourcetype:1; + uint32_t getetag:1; + uint32_t creationdate:1; +}; + +struct WebdavResponse { + WebdavOperation *op; + + WebdavResource* (*addresource)(WebdavResponse*, const char*); +}; + +struct WebdavResource { + char *href; + + WSBool isclosed; + + int err; + + /* + * int addprop(WebdavResource *res, WebdavProperty *property, int status); + * + * Adds a property to the resource + */ + int (*addproperty)(WebdavResource*, WebdavProperty*, int); + + /* + * int close(WebdavResource *res); + * + * Closes a resource object + */ + int (*close)(WebdavResource*); +}; + +struct WebdavBackend { + /* + * int propfind_init( + * WebdavPropfindRequest *rq, + * const char *path, + * WebdavPList **outplist); + * + * Initializes a propfind request. This is called once for each propfind + * request and should initialize everything needed for generating the + * multistatus response. + * + */ + int (*propfind_init)(WebdavPropfindRequest *, const char *, WebdavPList **); + + /* + * int propfind_do( + * WebdavPropfindRequest *rq, + * WebdavResponse *response, + * VFS_DIR parent, + * WebdavResource *resource, + * struct stat *s); + * + * This function is called for the requsted resource and for all children + * if WS_PROPFIND_NO_VFS_CHILDREN is not set. + */ + int (*propfind_do)( + WebdavPropfindRequest *, + WebdavResponse *, + VFS_DIR, + WebdavResource *, + struct stat *); + + /* + * int propfind_finish(WebdavPropfindRequest *rq); + * + * Finishes a propfind request. + */ + int (*propfind_finish)(WebdavPropfindRequest *); + + /* + * int proppatch_do( + * WebdavProppatchRequest *request, + * WebdavResource *response, + * VFSFile *file, + * WebdavPList **out_set, + * WebdavPList **out_remove); + * + * Modifies properties of the requsted resource. + */ + int (*proppatch_do)( + WebdavProppatchRequest *, + WebdavResource *, + VFSFile *, + WebdavPList **, + WebdavPList **); + + /* + * int proppatch_finish( + * WebdavProppatchRequest *request, + * WebdavResource *response, + * VFSFile *file, + * WSBool commit); + * + * Called after all proppatch_do functions of all backends are executed + * and should either permanently store the properties (commit == true) or + * revert all changed (commit == false). + */ + int (*proppatch_finish)( + WebdavProppatchRequest *, + WebdavResource *, + VFSFile *, + WSBool); + + /* + * int opt_mkcol(WebdavVFSRequest *request, WSBool *out_created); + * + * Optional mkcol callback that is called before vfs_mkdir. If the function + * sets out_created to TRUE, vfs_mkdir will not be executed. + */ + int (*opt_mkcol)(WebdavVFSRequest *, WSBool *); + + /* + * int opt_mkcol_finish(WebdavVFSRequest *request, WSBool success); + * + * Optional callback for finishing a MKCOL request. + */ + int(*opt_mkcol_finish)(WebdavVFSRequest *, WSBool); + + /* + * int opt_delete(WebdavVFSRequest *request, WSBool *out_deleted); + * + * Optional delete callback that is called once before any VFS deletions. + * When the callback sets out_deleted to TRUE, no VFS unlink operations + * will be done. + * + */ + int (*opt_delete)(WebdavVFSRequest *, WSBool *); + + /* + * int opt_delete_finish(WebdavVFSRequest *request, WSBool success); + * + * Optional callback for finishing a DELETE request. + */ + int (*opt_delete_finish)(WebdavVFSRequest *, WSBool); + + /* + * See the WS_WEBDAV_* macros for informations about the settings + */ + uint32_t settings; + + + /* + * next Backend + */ + WebdavBackend *next; +}; + +/* + * gets the requested depth + * + * in case of infinity, -1 is returned + * if no depth is specified, 0 is returned + */ +int webdav_getdepth(Request *rq); + +int webdav_plist_add( + pool_handle_t *pool, + WebdavPList **begin, + WebdavPList **end, + WebdavProperty *prop); + +WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list); +WebdavPList* webdav_plist_clone_s( + pool_handle_t *pool, + WebdavPList *list, + size_t *newlen); + +size_t webdav_plist_size(WebdavPList *list); + +int webdav_nslist_add( + pool_handle_t *pool, + WebdavNSList **begin, + WebdavNSList **end, + WSNamespace *ns); + +WebdavPListIterator webdav_plist_iterator(WebdavPList **list); +int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur); +void webdav_plist_iterator_remove_current(WebdavPListIterator *i); + +WSNamespace* webdav_dav_namespace(void); +WebdavProperty* webdav_dav_property( + pool_handle_t *pool, + const char *name); + +int webdav_property_set_value( + WebdavProperty *property, + pool_handle_t *pool, + char *value); + +WebdavVFSProperties webdav_vfs_properties( + WebdavPList **plistInOut, + WSBool removefromlist, + uint32_t flags); + +int webdav_add_vfs_properties( + WebdavResource *res, + pool_handle_t *pool, + WebdavVFSProperties properties, + struct stat *s); + +int wsxml_iterator( + pool_handle_t *pool, + WSXmlNode *node, + wsxml_func begincb, + wsxml_func endcb, + void *udata); + +WebdavNSList* wsxml_get_required_namespaces( + pool_handle_t *pool, + WSXmlNode *node, + int *error); #ifdef __cplusplus }
--- a/src/server/test/main.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/test/main.c Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,10 +39,12 @@ #include "../util/plist.h" #include "../util/date.h" -#include <ucx/string.h> +#include <ucx/test.h> -static int std_pipe_fds[2]; -static WSBool is_daemon; +#include "vfs.h" +#include "writer.h" +#include "xml.h" +#include "webdav.h" void test() { @@ -50,7 +52,7 @@ // needed for linking WSBool main_is_daemon(void) { - return is_daemon; + return 0; } int main(int argc, char **argv) { @@ -59,7 +61,55 @@ //test(); printf("%s", "Webserver Test Suite\n====================\n\n"); - + + UcxTestSuite* suite = ucx_test_suite_new(); + + // vfs tests + ucx_test_register(suite, test_vfs_open); + ucx_test_register(suite, test_vfs_mkdir); + ucx_test_register(suite, test_vfs_opendir); + ucx_test_register(suite, test_vfs_readdir); + ucx_test_register(suite, test_vfs_unlink); + ucx_test_register(suite, test_vfs_rmdir); + + // writer tests + ucx_test_register(suite, test_writer_putc); + ucx_test_register(suite, test_writer_flush); + ucx_test_register(suite, test_writer_put); + + // xml tests + ucx_test_register(suite, test_wsxml_iterator); + ucx_test_register(suite, test_wsxml_get_required_namespaces); + ucx_test_register(suite, test_wsxml_write_nodes); + + // webdav tests + ucx_test_register(suite, test_webdav_plist_add); + ucx_test_register(suite, test_webdav_plist_size); + ucx_test_register(suite, test_propfind_parse); + ucx_test_register(suite, test_proppatch_parse); + ucx_test_register(suite, test_lock_parse); + ucx_test_register(suite, test_rqbody2buffer); + ucx_test_register(suite, test_webdav_plist_iterator); + ucx_test_register(suite, test_webdav_plist_iterator_remove_current); + ucx_test_register(suite, test_msresponse_addproperty); + ucx_test_register(suite, test_webdav_propfind_init); + ucx_test_register(suite, test_webdav_op_propfind_begin); + ucx_test_register(suite, test_webdav_op_propfind_children); + ucx_test_register(suite, test_proppatch_msresponse); + ucx_test_register(suite, test_msresponse_addproperty_with_errors); + ucx_test_register(suite, test_webdav_op_proppatch); + ucx_test_register(suite, test_webdav_vfs_op_do); + ucx_test_register(suite, test_webdav_delete); + + // webdav methods + ucx_test_register(suite, test_webdav_propfind); + ucx_test_register(suite, test_webdav_proppatch); + ucx_test_register(suite, test_webdav_put); + + // run tests + ucx_test_run(suite, stdout); + fflush(stdout); + return EXIT_SUCCESS; }
--- a/src/server/test/objs.mk Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/test/objs.mk Tue Aug 25 12:07:56 2020 +0200 @@ -31,6 +31,11 @@ TEST_OBJPRE = $(OBJ_DIR)$(TEST_SRC_DIR) TESTOBJ = main.o +TESTOBJ += testutils.o +TESTOBJ += webdav.o +TESTOBJ += vfs.o +TESTOBJ += xml.o +TESTOBJ += writer.o TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%) TESTSOURCE = $(TESTOBJ:%.o=test/%.c)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/testutils.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,170 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <ucx/string.h> +#include <ucx/utils.h> + +#include "../util/pblock.h" + +#include "../util/io.h" + +#include "testutils.h" + +Session* testutil_session(void) { + pool_handle_t *pool = pool_create(); + NSAPISession *sn = nsapisession_create(pool); + sn->connection = testutil_dummy_connection(pool); + + return &sn->sn; +} + +Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri) { + NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); + + HTTPRequest httprequest; + ZERO(&httprequest, sizeof(HTTPRequest)); + request_initialize(pool, &httprequest, rq); + + sstr_t clf = ucx_sprintf("%s %s HTTP/1.1", method, uri); + pblock_kvinsert( + pb_key_clf_request, + clf.ptr, + clf.length, + rq->rq.reqpb); + free(clf.ptr); + + pblock_nvinsert( + "method", + method, + rq->rq.reqpb); + + pblock_nvinsert( + "protocol", + "HTTP/1.1", + rq->rq.reqpb); + + pblock_nvinsert("uri", uri, rq->rq.reqpb); + + return &rq->rq; +} + +static int dummyconn_read(Connection *conn, void *buf, int len) { + return len; +} + +static int dummyconn_write(Connection *conn, const void *buf, int len) { + return len; +} + +static void dummyconn_close(Connection *conn) { + +} + + +Connection* testutil_dummy_connection(pool_handle_t *pool) { + Connection *conn = pool_malloc(pool, sizeof(Connection)); + ZERO(conn, sizeof(Connection)); + conn->read = dummyconn_read; + conn->write = dummyconn_write; + conn->close = dummyconn_close; + return conn; +} + +void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len) { + sstr_t cl = ucx_sprintf("%d", (int)len); + pblock_nvreplace("content-length", cl.ptr, rq->headers); + free(cl.ptr); + + netbuf *inbuf = pool_malloc(sn->pool, sizeof(netbuf)); + inbuf->sd = NULL; + inbuf->inbuf = pool_malloc(sn->pool, len); + inbuf->pos = 0; + inbuf->maxsize = len; + inbuf->cursize = len; + sn->inbuf = inbuf; + + memcpy(inbuf->inbuf, body, len); +} + +void testutil_destroy_session(Session *sn) { + pool_destroy(sn->pool); +} + + +static ssize_t test_io_write(IOStream *io, void *buf, size_t size) { + TestIOStream *st = (TestIOStream*)io; + return ucx_buffer_write(buf, 1, size, st->buf); +} + +static ssize_t test_io_writev(IOStream *io, struct iovec *iovec, int iovctn) { + return -1; +} + +static ssize_t test_io_read(IOStream *io, void *buf, size_t size) { + return -1; +} + +static void test_io_close(IOStream *io) { + +} + +static void test_io_finish(IOStream *io) { + +} + +static void test_io_setmode(IOStream *io, int mode) { + +} + +static int test_io_poll(IOStream *io, EventHandler *ev, int events , Event *event) { + return 1; +} + +TestIOStream* testutil_iostream(size_t size, int autoextend) { + TestIOStream *stream = calloc(1, sizeof(TestIOStream)); + int flags = 0; + if(autoextend) { + flags = UCX_BUFFER_AUTOEXTEND; + } + stream->buf = ucx_buffer_new(NULL, size, flags); + + stream->io.st.write = test_io_write; + stream->io.st.writev = test_io_writev; + stream->io.st.close = test_io_close; + stream->io.st.finish = test_io_finish; + + return stream; +} + +void testutil_iostream_destroy(TestIOStream *stream) { + ucx_buffer_free(stream->buf); + free(stream); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/testutils.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,66 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include "../public/nsapi.h" +#include "../daemon/httprequest.h" + +#include "../util/io.h" +#include <ucx/buffer.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TestIOStream { + HttpStream io; + UcxBuffer *buf; +} TestIOStream; + +Session* testutil_session(void); + +Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri); + +Connection* testutil_dummy_connection(pool_handle_t *pool); + +void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len); + +void testutil_destroy_session(Session *sn); + +TestIOStream* testutil_iostream(size_t size, int autoextend); +void testutil_iostream_destroy(TestIOStream *stream); + + +#ifdef __cplusplus +} +#endif + +#endif /* TESTUTILS_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/vfs.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,557 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <ucx/string.h> +#include <ucx/list.h> +#include <ucx/map.h> + +#include "../daemon/session.h" + +#include "testutils.h" + +#include "vfs.h" + +typedef struct TestVFS { + UcxMap *files; + int count_unlink; + int count_rmdir; +} TestVFS; + +typedef struct TestVFSFile { + VFSFile file; + sstr_t path; + int isdir; + UcxBuffer *content; +} TestVFSFile; + +typedef struct TestVFSDir { + VFSDir dir; + TestVFSFile *file; + UcxMapIterator i; + sstr_t name; +} TestVFSDir; + +/* dir io */ + +static char* test_resource_name(char *url) { + sstr_t urlstr = sstr(url); + if(urlstr.ptr[urlstr.length-1] == '/') { + urlstr.length--; + } + sstr_t resname = sstrrchr(urlstr, '/'); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { + TestVFS *vfs = dir->ctx->vfs->instance; + TestVFSDir *vfsdir = (TestVFSDir*)dir; + + sstr_t prefix = sstrcat(2, vfsdir->file->path, S("/")); + + TestVFSFile *file = NULL; + UCX_MAP_FOREACH(key, file, vfsdir->i) { + sstr_t file_path = sstrcat( + 2, + prefix, + sstr(test_resource_name(file->path.ptr))); + void *m = ucx_map_get(vfs->files, ucx_key(file_path.ptr, file_path.length)); + // don't ask why alfree and not free() + alfree(ucx_default_allocator(), file_path.ptr); + if(m) { + break; + } else { + file = NULL; + } + } + free(prefix.ptr); + + if(file) { + vfsdir->name = sstrdup_a( + session_get_allocator(dir->ctx->sn), + sstr(test_resource_name(file->path.ptr))); + ZERO(entry, sizeof(VFS_ENTRY)); + entry->name = vfsdir->name.ptr; + + if(getstat) { + ZERO(&entry->stat, sizeof(struct stat)); + if(file->isdir) { + entry->stat.st_mode = S_IFDIR; + } + } + + return 1; + } else { + return 0; + } +} + +void testvfs_dir_close(VFS_DIR dir) { + TestVFSDir *testdir = (TestVFSDir*)dir; + pool_free(testdir->dir.ctx->sn->pool, dir); + +} + + ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_read(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_write(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_read(fd, buf, nbyte); + } + + ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_write(fd, buf, nbyte); + } + + off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) { + TestVFSFile *file = (TestVFSFile*)fd; + ucx_buffer_seek(file->content, offset, whence); + return (off_t)file->content->pos; + } + + void testvfs_close(SYS_FILE fd) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = 0; + } + +VFS_IO test_file_io = { + testvfs_read, + testvfs_write, + testvfs_pread, + testvfs_pwrite, + testvfs_seek, + testvfs_close, + NULL, /* aio_read */ + NULL /* aio_write */ +}; + +VFS_DIRIO test_dir_io = { + testvfs_readdir, + testvfs_dir_close +}; + + +/* vfs funcs */ + +SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + if((oflags & O_CREAT) == O_CREAT) { + file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile)); + ZERO(file, sizeof(TestVFSFile)); + file->path = sstrdup_a(session_get_allocator(ctx->sn), s_path); + file->file.io = &test_file_io; + + file->content = pool_calloc(ctx->sn->pool, 1, sizeof(UcxBuffer)); + file->content->capacity = 2048; + file->content->space = pool_malloc(ctx->sn->pool, file->content->capacity); + file->content->flags = 0; + file->content->pos = 0; + file->content->size = 0; + + ucx_map_sstr_put(vfs->files, s_path, file); + } else { + ctx->vfs_errno = ENOENT; + } + } + + return (SYS_FILE)file; +} + +int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return 1; + } + + ZERO(buf, sizeof(struct stat)); + if(file->isdir) { + buf->st_mode = S_IFDIR; + } + + return 0; +} + +int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { + return 0; +} + +VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return NULL; + } + + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = (TestVFSFile*)fd; + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +int testvfs_mkdir(VFSContext *ctx, const char *path) { + SYS_FILE fd = testvfs_open(ctx, path, O_CREAT); + if(!fd) { + return 1; + } + + TestVFSFile *file = (TestVFSFile*)fd; + file->isdir = 1; + + return 0; +} + +int testvfs_unlink(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = ucx_map_cstr_get(vfs->files, path); + if(!file) { + return 1; + } + + if(file->isdir) { + return 1; + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_unlink++; + return 0; +} + +int testvfs_rmdir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *dir = ucx_map_cstr_get(vfs->files, path); + if(!dir) { + ctx->vfs_errno = ENOENT; + return 1; + } + + if(!dir->isdir) { + return 1; + } + + UcxMapIterator i = ucx_map_iterator(vfs->files); + TestVFSFile *f; + UCX_MAP_FOREACH(key, f, i) { + if(f->path.length > dir->path.length && sstrprefix(f->path, dir->path)){ + return 1; // dir not empty + } + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_rmdir++; + return 0; +} + +static VFS testVFSClass = { + testvfs_open, + testvfs_stat, + testvfs_fstat, + testvfs_opendir, + testvfs_fdopendir, + testvfs_mkdir, + testvfs_unlink, + testvfs_rmdir, + 0, + NULL +}; + + +VFS* testvfs_create(Session *sn) { + TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS)); + vfs->count_unlink = 0; + vfs->count_rmdir = 0; + vfs->files = ucx_map_new_a(session_get_allocator(sn), 64); + + testVFSClass.instance = vfs; + return &testVFSClass; +} + + +/* ------------------------------------------------------------------------- */ +// +// VFS Tests +// +/* ------------------------------------------------------------------------- */ + +UCX_TEST(test_vfs_open) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(vfs, "vfs is NULL"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file1", 0); + UCX_TEST_ASSERT(f2, "f2 not opened"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_mkdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + SYS_FILE fd = vfs_open(vfs, "/dir", 0); + UCX_TEST_ASSERT(fd, "no fd"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_opendir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "no dir"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_readdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + // add some test file to /dir + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "dir not opened"); + + UcxMap *files = ucx_map_new(8); + + VFSEntry entry; + while(vfs_readdir(dir, &entry)) { + ucx_map_cstr_put(files, entry.name, dir); + } + + UCX_TEST_ASSERT(files->count == 4, "wrong files count"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file1"), "file1 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file2"), "file2 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file3"), "file3 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file4"), "file4 missing"); + + ucx_map_free(files); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_unlink) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT); + UCX_TEST_ASSERT(f1, "f2 not opened"); + + SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT); + UCX_TEST_ASSERT(f1, "f3 not opened"); + + // test unlink + err = vfs_unlink(vfs, "/file1"); + UCX_TEST_ASSERT(err == 0, "unlink /file1 failed"); + err = vfs_unlink(vfs, "/dir1/file3"); + UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed"); + + err = vfs_unlink(vfs, "/filex"); + UCX_TEST_ASSERT(err != 0, "unlink /filex should fail"); + + // check if files were removed + SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted"); + SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted"); + + // file2 should still be there + SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY); + UCX_TEST_ASSERT(o2, "/file2 deleted"); + + // check if dir unlink fails + err = vfs_unlink(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_rmdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail"); + err = vfs_rmdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed"); + + err = vfs_unlink(vfs, "/dir1/file1"); + UCX_TEST_ASSERT(err == 0, "unlink failed"); + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/vfs.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_VFS_H +#define TEST_VFS_H + +#include "../public/nsapi.h" +#include "../public/vfs.h" + +#include <ucx/test.h> + +#ifdef __cplusplus +extern "C" { +#endif + +VFS* testvfs_create(Session *sn); + +UCX_TEST(test_vfs_open); +UCX_TEST(test_vfs_mkdir); +UCX_TEST(test_vfs_opendir); +UCX_TEST(test_vfs_readdir); +UCX_TEST(test_vfs_unlink); +UCX_TEST(test_vfs_rmdir); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_VFS_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/webdav.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,1762 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "testutils.h" + +#include "../webdav/requestparser.h" +#include "../webdav/webdav.h" +#include "../webdav/multistatus.h" +#include "../webdav/operation.h" + +#include "vfs.h" +#include "webdav.h" + +static int webdav_is_initialized = 0; + +/* ----------------------------- Test Backends --------------------------*/ + +static int backend2_init_called = 0; +static int backend2_propfind_do_count = 0; +static int backend2_propfind_finish_called = 0; +static int backend2_proppatch_commit = 0; +static int backend2_proppatch_do_count = 0; +static int backend2_proppatch_finish_count = 0; + +// backend2 +static int backend2_propfind_init( + WebdavPropfindRequest *propfind, + const char *path, + WebdavPList **outPList) +{ + backend2_init_called = 1; + return 0; +} + +static int backend2_propfind_do( + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + backend2_propfind_do_count++; + return 0; +} + +static int backend2_propfind_finish(WebdavPropfindRequest *propfind) { + backend2_propfind_finish_called = 1; + return 0; +} + +static int backend2_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove) +{ + backend2_proppatch_do_count++; + + if(*out_remove) { + return 1; // backend1 should remove all remove-props + } + + WebdavPListIterator i = webdav_plist_iterator(out_set); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + if(!strcmp(cur->property->name, "a")) { + // property 'a' should already be removed by backend1 + return 1; + } else if(!strcmp(cur->property->name, "abort")) { + return 1; // test abort + } + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + + return 0; +} + +static int backend2_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + backend2_proppatch_finish_count++; + backend2_proppatch_commit = commit; + return 0; +} + +static WebdavBackend backend2 = { + backend2_propfind_init, + backend2_propfind_do, + backend2_propfind_finish, + backend2_proppatch_do, + backend2_proppatch_finish, + NULL, // opt_mkcol + NULL, // opt_mkcol_finish + NULL, // opt_delete + NULL, // opt_delete_finish + 0, + NULL +}; + +// backend1 + +static int backend1_init_called = 0; +static int backend1_propfind_do_count = 0; +static int backend1_propfind_finish_called = 0; +static int backend1_proppatch_commit = 0; +static int backend1_proppatch_do_count = 0; +static int backend1_proppatch_finish_count = 0; + + +static int backend1_propfind_init( + WebdavPropfindRequest *propfind, + const char *path, + WebdavPList **outPList) +{ + backend1_init_called = 1; + + WebdavPList *plist = *outPList; + WebdavProperty *p = plist->property; + if(!strcmp(p->name, "displayname")) { + plist->next->prev = NULL; + *outPList = plist->next; // remove first item from plist + } else { + return 1; + } + + return 0; +} + +static int backend1_propfind_do( + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + backend1_propfind_do_count++; + return 0; +} + +static int backend1_propfind_finish(WebdavPropfindRequest *propfind) { + backend1_propfind_finish_called = 1; + return 0; +} + +static int backend1_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove) +{ + backend1_proppatch_do_count++; + + // remove everything from out_remove + WebdavPListIterator i = webdav_plist_iterator(out_remove); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + + // remove property 'a' and fail at property 'fail' + i = webdav_plist_iterator(out_set); + while(webdav_plist_iterator_next(&i, &cur)) { + if(!strcmp(cur->property->name, "fail")) { + response->addproperty(response, cur->property, 403); + webdav_plist_iterator_remove_current(&i); + } else if(!strcmp(cur->property->name, "a")) { + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + } + + return 0; +} + +static int backend1_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + backend1_proppatch_finish_count++; + backend1_proppatch_commit = commit; + return 0; +} + +WebdavBackend backend1 = { + backend1_propfind_init, + backend1_propfind_do, + backend1_propfind_finish, + backend1_proppatch_do, + backend1_proppatch_finish, + NULL, // opt_mkcol + NULL, // opt_mkcol_finish + NULL, // opt_delete + NULL, // opt_delete_finish + 0, + &backend2 +}; + +static void reset_backends(void) { + backend1_init_called = 0; + backend1_propfind_do_count = 0; + backend1_propfind_finish_called = 0; + backend1_proppatch_commit = 0; + backend1_proppatch_do_count = 0; + backend1_proppatch_finish_count = 0; + backend2_init_called = 0; + backend2_propfind_do_count = 0; + backend2_propfind_finish_called = 0; + backend2_proppatch_commit = 0; + backend2_proppatch_do_count = 0; + backend2_proppatch_finish_count = 0; +} + +/* ----------------------------------------------------------------------*/ + + +static int test_init( + Session **out_sn, + Request **out_rq, + WebdavPropfindRequest **out_propfind, + const char *xml) +{ + if(!webdav_is_initialized) { + if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) { + return 1; + } + webdav_is_initialized = 1; + } + + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPFIND", "/"); + + int error = 0; + + WebdavPropfindRequest *propfind = propfind_parse( + sn, + rq, + xml, + strlen(xml), + &error); + + if(error) { + return 1; + } + + if(!propfind || !propfind->properties) { + return 1; + } + + *out_sn = sn; + *out_rq = rq; + *out_propfind = propfind; + return 0; +} + +static WebdavOperation* test_propfind_op( + Session **out_sn, + Request **out_rq, + const char *xml) +{ + WebdavPropfindRequest *propfind; + if(test_init(out_sn, out_rq, &propfind, xml)) { + return NULL; + } + + Multistatus *ms = multistatus_response(*out_sn, *out_rq); + if(!ms) { + return NULL; + } + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + UcxList *requests = NULL; + + // Initialize all Webdav Backends + if(webdav_propfind_init(&backend1, propfind, "/", &requests)) { + return NULL; + } + + return webdav_create_propfind_operation( + (*out_sn), + (*out_rq), + &backend1, + propfind->properties, + requests, + response); +} + + +UCX_TEST(test_webdav_plist_add) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + WebdavPList *begin = NULL; + WebdavPList *end = NULL; + + WebdavProperty p1, p2, p3; + ZERO(&p1, sizeof(WebdavProperty)); + ZERO(&p2, sizeof(WebdavProperty)); + ZERO(&p3, sizeof(WebdavProperty)); + int r; + + r = webdav_plist_add(sn->pool, &begin, &end, &p1); + + UCX_TEST_ASSERT(r == 0, "add 1 failed"); + UCX_TEST_ASSERT(begin && end, "ptrs are NULL"); + UCX_TEST_ASSERT(begin == end, "begin != end"); + UCX_TEST_ASSERT(begin->prev == NULL, "begin->prev not NULL"); + UCX_TEST_ASSERT(begin->next == NULL, "begin->next not NULL"); + + r = webdav_plist_add(sn->pool, &begin, &end, &p2); + + UCX_TEST_ASSERT(r == 0, "add 2 failed"); + UCX_TEST_ASSERT(begin && end, "add2: ptrs are NULL"); + UCX_TEST_ASSERT(begin->next, "begin->next is NULL"); + UCX_TEST_ASSERT(begin->next == end, "begin->next != end"); + UCX_TEST_ASSERT(end->prev = begin, "end->prev != begin"); + UCX_TEST_ASSERT(begin->prev == NULL, "add2: begin->prev not NULL"); + UCX_TEST_ASSERT(end->next == NULL, "add2: end->next not NULL"); + + r = webdav_plist_add(sn->pool, &begin, &end, &p3); + + UCX_TEST_ASSERT(r == 0, "add 3 failed"); + UCX_TEST_ASSERT(begin && end, "add3: ptrs are NULL"); + UCX_TEST_ASSERT(begin->next == end->prev, "begin->next != end->prev"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_plist_size) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + WebdavPList *begin = NULL; + WebdavPList *end = NULL; + + WebdavProperty p1, p2, p3; + ZERO(&p1, sizeof(WebdavProperty)); + ZERO(&p2, sizeof(WebdavProperty)); + ZERO(&p3, sizeof(WebdavProperty)); + int r; + + UCX_TEST_ASSERT(webdav_plist_size(begin) == 0, "size != 0"); + r = webdav_plist_add(sn->pool, &begin, &end, &p1); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 1, "size != 1"); + r = webdav_plist_add(sn->pool, &begin, &end, &p2); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 2, "size != 2"); + r = webdav_plist_add(sn->pool, &begin, &end, &p3); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 3, "size != 3"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_propfind_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPFIND", "/"); + + UCX_TEST_BEGIN + + int error = 0; + + // + // ----------------- TEST_PROPFIND1 ----------------- + // test basic propfind request + WebdavPropfindRequest *p1 = propfind_parse( + sn, + rq, + TEST_PROPFIND1, + strlen(TEST_PROPFIND1), + &error); + + UCX_TEST_ASSERT(p1, "p1 is NULL"); + UCX_TEST_ASSERT(p1->properties, "p1: no props"); + UCX_TEST_ASSERT(!p1->allprop, "p1: allprop is TRUE"); + UCX_TEST_ASSERT(!p1->propname, "p1: propname is TRUE"); + UCX_TEST_ASSERT(p1->propcount == 6, "p1: wrong propcount"); + + // property 1: DAV:displayname + WebdavPList *elm = p1->properties; + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "displayname"), + "p1: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 1 has wrong namespace"); + + // property 2: DAV:getcontentlength + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 2 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "getcontentlength"), + "p1: property 2 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 2 has wrong namespace"); + + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 3 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 4 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 5 missing"); + + // property 6: DAV:getetag + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 6 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "getetag"), + "p1: property 6 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 6 has wrong namespace"); + UCX_TEST_ASSERT(!elm->next, "p1: should not have property 7"); + + // + // ----------------- TEST_PROPFIND2 ----------------- + // test with multiple namespaces + WebdavPropfindRequest *p2 = propfind_parse( + sn, + rq, + TEST_PROPFIND2, + strlen(TEST_PROPFIND2), + &error); + + UCX_TEST_ASSERT(p2, "p2 is NULL"); + UCX_TEST_ASSERT(p2->properties, "p2: no props"); + UCX_TEST_ASSERT(!p2->allprop, "p2: allprop is TRUE"); + UCX_TEST_ASSERT(!p2->propname, "p2: propname is TRUE"); + + // property 1: DAV:resourcetype + elm = p2->properties; + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "resourcetype"), + "p2: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p2: property 1 has wrong namespace"); + + // property 2: X:testprop + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 2 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "testprop"), + "p2: property 2 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "http://example.com/"), + "p2: property 2 has wrong namespace"); + + // property 3: X:name + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 3 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "name"), + "p2: property 3 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "http://example.com/"), + "p2: property 3 has wrong namespace"); + + // property 4: Z:testprop + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 4 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "testprop"), + "p2: property 4 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "testns"), + "p2: property 4 has wrong namespace"); + + + // + // ----------------- TEST_PROPFIND3 ----------------- + // test allprop + WebdavPropfindRequest *p3 = propfind_parse(sn, rq, TEST_PROPFIND3, strlen(TEST_PROPFIND3), &error); + + UCX_TEST_ASSERT(p3, "p3 is NULL"); + UCX_TEST_ASSERT(!p3->properties, "p2: has props"); + UCX_TEST_ASSERT(p3->allprop, "p2: allprop is FALSE"); + UCX_TEST_ASSERT(!p3->propname, "p2: propname is TRUE"); + UCX_TEST_ASSERT(p3->propcount == 0, "p2: wrong propcount"); + + + // + // ----------------- TEST_PROPFIND4 ----------------- + // test propname + WebdavPropfindRequest *p4 = propfind_parse(sn, rq, TEST_PROPFIND4, strlen(TEST_PROPFIND4), &error); + + UCX_TEST_ASSERT(p4, "p4 is NULL"); + UCX_TEST_ASSERT(!p4->properties, "p2: has props"); + UCX_TEST_ASSERT(!p4->allprop, "p2: allprop is TRUE"); + UCX_TEST_ASSERT(p4->propname, "p2: propname is FALSE"); + + + // + // ----------------- TEST_PROPFIND5 ----------------- + // test duplicate check + WebdavPropfindRequest *p5 = propfind_parse(sn, rq, TEST_PROPFIND5, strlen(TEST_PROPFIND5), &error); + + UCX_TEST_ASSERT(p5, "p5 is NULL"); + UCX_TEST_ASSERT(p5->properties, "p5: no props"); + UCX_TEST_ASSERT(!p5->allprop, "p5: allprop is TRUE"); + UCX_TEST_ASSERT(!p5->propname, "p5: propname is TRUE"); + UCX_TEST_ASSERT(p5->propcount == 4, "p5: wrong propcount"); + + // property 1: DAV:displayname + elm = p5->properties; + UCX_TEST_ASSERT(elm, "p5: property 1 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "displayname"), + "p5: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p5: property 1 has wrong namespace"); + + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 2 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 3 missing"); + + // property 4: DAV:resourcetype + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 4 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "resourcetype"), + "p5: property 4 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p5: property 4 has wrong namespace"); + + + // + // ----------------- TEST_PROPFIND6 ----------------- + // test prop/allprop mix + WebdavPropfindRequest *p6 = propfind_parse(sn, rq, TEST_PROPFIND6, strlen(TEST_PROPFIND6), &error); + + UCX_TEST_ASSERT(p6, "p5 is NULL"); + UCX_TEST_ASSERT(!p6->properties, "p5: has props"); + UCX_TEST_ASSERT(p6->allprop, "p5: allprop is FALSE"); + UCX_TEST_ASSERT(!p6->propname, "p5: propname is TRUE"); + UCX_TEST_ASSERT(p6->propcount == 0, "p5: wrong propcount"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_proppatch_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPPATCH", "/"); + + UCX_TEST_BEGIN + int error = 0; + + WebdavProppatchRequest *p1 = proppatch_parse(sn, rq, TEST_PROPPATCH1, strlen(TEST_PROPPATCH1), &error); + + UCX_TEST_ASSERT(p1->set, "p1: missing set props"); + UCX_TEST_ASSERT(!p1->remove, "p1: has remove props"); + UCX_TEST_ASSERT(p1->setcount == 2, "p1: wrong setcount"); + UCX_TEST_ASSERT(p1->set->next, "p1: set plist broken"); + UCX_TEST_ASSERT(!p1->set->next->next, "p1: set plist has no end"); + UCX_TEST_ASSERT(p1->set->property, "p1: missing property ptr in plist"); + UCX_TEST_ASSERT( + !strcmp(p1->set->property->name, "test"), + "p1: wrong property 1 name"); + + WebdavProppatchRequest *p2 = proppatch_parse(sn, rq, TEST_PROPPATCH2, strlen(TEST_PROPPATCH2), &error); + + UCX_TEST_ASSERT(p2->set, "p2: missing set props"); + UCX_TEST_ASSERT(p2->remove, "p2: missing remove props"); + UCX_TEST_ASSERT(p2->setcount == 4, "p2: wrong setcount"); + UCX_TEST_ASSERT(p2->removecount == 1, "p2: wrong removecount"); + + UCX_TEST_ASSERT( + !strcmp((char*)p2->set->property->namespace->href, "http://example.com/"), + "p2: set property 1: wrong namespace"); + UCX_TEST_ASSERT( + !strcmp(p2->set->property->name, "a"), + "p2: set property 1: wrong name"); + WSXmlNode *p2set1 = p2->set->property->value.node; + UCX_TEST_ASSERT( + p2set1->type == WS_NODE_TEXT, + "p2: set property 1: wrong type"); + UCX_TEST_ASSERT( + p2set1->content, + "p2: set property 1: no text"); + UCX_TEST_ASSERT( + !strcmp((char*)p2set1->content, "test"), + "p2: set property 1: wrong value"); + + WSXmlNode *p2set3 = p2->set->next->next->property->value.node; + UCX_TEST_ASSERT(p2set3, "p2: set property 3 missing"); + UCX_TEST_ASSERT( + p2set3->type == WS_NODE_TEXT, + "p2: set property 3: wrong type"); + UCX_TEST_ASSERT( + p2set3->next, + "p2: set property 3: missing element X:name"); + + UCX_TEST_ASSERT( + xmlHasProp(p2set3->next, BAD_CAST"test"), + "p2: set property 3: missing attribute 'test'"); + + UCX_TEST_ASSERT( + xmlHasProp(p2set3->next, BAD_CAST"abc"), + "p2: set property 3: missing attribute 'abc"); + + xmlChar *value1 = xmlGetProp(p2set3->next, BAD_CAST"test"); + UCX_TEST_ASSERT( + !strcmp((char*) value1, "test1"), + "p2: set property 3: wrong attribute value 1"); + xmlFree(value1); + + xmlChar *value2 = xmlGetProp(p2set3->next, BAD_CAST"abc"); + UCX_TEST_ASSERT( + !strcmp((char*) value2, "def"), + "p2: set property 3: wrong attribute value 2"); + xmlFree(value2); + + UCX_TEST_ASSERT( + !strcmp(p2->remove->property->name, "e"), + "p2: wrong remove property"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_lock_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "LOCK", "/"); + + UCX_TEST_BEGIN + int error = 0; + + WebdavLockRequest *l1 = lock_parse(sn, rq, TEST_LOCK1, strlen(TEST_LOCK1), &error); + + UCX_TEST_ASSERT(l1, "l1 is NULL"); + UCX_TEST_ASSERT(l1->type == WEBDAV_LOCK_WRITE, "l1: wrong type"); + UCX_TEST_ASSERT(l1->scope == WEBDAV_LOCK_SHARED, "l1: wrong scope"); + UCX_TEST_ASSERT(l1->owner, "l1: owner is NULL"); + UCX_TEST_ASSERT(!strcmp((char*)l1->owner->content, "User"), "l1: wrong owner"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_rqbody2buffer) { + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + // + // TEST 1 + sn = testutil_session(); + rq = testutil_request(sn->pool, "PUT", "/"); + testutil_request_body(sn, rq, "Hello World!", 12); + + UcxBuffer *b1 = rqbody2buffer(sn, rq); + UCX_TEST_ASSERT(b1->size == 12, "b1: wrong size"); + UCX_TEST_ASSERT(!memcmp(b1->space,"Hello World!",12), "b1: wrong content"); + + ucx_buffer_free(b1); + testutil_destroy_session(sn); + + // + // TEST 2 + size_t len1 = 25000; + unsigned char *body1 = malloc(len1); + for(int i=0;i<len1;i++) { + body1[i] = i; + } + sn = testutil_session(); + rq = testutil_request(sn->pool, "PUT", "/"); + testutil_request_body(sn, rq, (char*)body1, len1); + + UcxBuffer *b2 = rqbody2buffer(sn, rq); + UCX_TEST_ASSERT(b2->size == len1, "b2: wrong size"); + UCX_TEST_ASSERT(!memcmp(b2->space, body1, len1), "b2: wrong content"); + + ucx_buffer_free(b2); + testutil_destroy_session(sn); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_plist_iterator) { + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + WebdavPList *properties = propfind->properties; + size_t count = 0; + + WebdavPListIterator i = webdav_plist_iterator(&properties); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + switch(i.index) { + case 0: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "displayname"), "wrong property 1"); + break; + } + case 1: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontentlength"), "wrong property 2"); + break; + } + case 2: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontenttype"), "wrong property 3"); + break; + } + case 3: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getlastmodified"), "wrong property 4"); + break; + } + case 4: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "resourcetype"), "wrong property 5"); + break; + } + case 5: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getetag"), "wrong property 6"); + break; + } + } + count++; + } + + UCX_TEST_ASSERT(count == propfind->propcount, "wrong count"); + + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_plist_iterator_remove_current) { + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + WebdavPList *properties1 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties2 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties3 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties4 = webdav_plist_clone(sn->pool, propfind->properties); + + WebdavPListIterator i; + WebdavPList *cur; + + // test removal of first element + i = webdav_plist_iterator(&properties1); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 0) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(!properties1->prev, "test1: prev not cleared"); + UCX_TEST_ASSERT(!strcmp(properties1->property->name, "getcontentlength"), "test1: wrong property"); + UCX_TEST_ASSERT(!strcmp(properties1->next->property->name, "getcontenttype"), "test1: wrong property 2"); + UCX_TEST_ASSERT(properties1->next->prev == properties1, "test1: wrong link"); + + // test removal of second element + i = webdav_plist_iterator(&properties2); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 1) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(!strcmp(properties2->next->property->name, "getcontenttype"), "test2: wrong property"); + UCX_TEST_ASSERT(properties2->next->prev == properties2, "test2: wrong link"); + UCX_TEST_ASSERT(webdav_plist_size(properties2) == 5, "test2: wrong size"); + + // remove last element + i = webdav_plist_iterator(&properties3); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 5) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(webdav_plist_size(properties3) == 5, "test3: wrong size"); + UCX_TEST_ASSERT(!strcmp(properties3->next->next->next->next->property->name, "resourcetype"), "test2: wrong property"); + + // remove all elements + i = webdav_plist_iterator(&properties4); + while(webdav_plist_iterator_next(&i, &cur)) { + webdav_plist_iterator_remove_current(&i); + switch(i.index) { + case 0: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontentlength"), "test4: wrong property 2"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (0)"); + break; + } + case 1: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontenttype"), "test4: wrong property 3"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (1)"); + break; + } + case 2: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getlastmodified"), "test4: wrong property 4"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (2)"); + break; + } + case 3: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "resourcetype"), "test4: wrong property 5"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (3)"); + break; + } + case 4: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getetag"), "test4: wrong property 6"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (4)"); + break; + } + default: { + UCX_TEST_ASSERT(i.index <= 5, "fail"); + } + } + } + + UCX_TEST_ASSERT(properties4 == NULL, "test4: list not NULL"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_msresponse_addproperty) { + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "init failed"); + UCX_TEST_ASSERT(op->response, "no response"); + + Multistatus *ms = (Multistatus*)op->response; + MSResponse *r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/"); + + WebdavProperty p1; + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + + WSNamespace ns1; + ZERO(&ns1, sizeof(WSNamespace)); + WSNamespace ns2; + ZERO(&ns2, sizeof(WSNamespace)); + ns1.prefix = (xmlChar*)"x1"; + ns1.href = (xmlChar*)"http://example.com/test/"; + ns2.prefix = (xmlChar*)"x2"; + ns2.href = (xmlChar*)"http://example.com/test/"; + + WebdavProperty dp1; + ZERO(&dp1, sizeof(WebdavProperty)); + dp1.name = "dup"; + dp1.namespace = &ns1; + dp1.value.text.str = "Hello"; + dp1.value.text.length = 5; + dp1.vtype = WS_VALUE_TEXT; + + WebdavProperty dp2; + ZERO(&dp2, sizeof(WebdavProperty)); + dp2.name = "dup"; + dp2.namespace = &ns1; + dp2.value.text.str = "Hello"; + dp2.value.text.length = 5; + dp2.vtype = WS_VALUE_TEXT; + + WebdavProperty dp3; + ZERO(&dp3, sizeof(WebdavProperty)); + dp3.name = "dup"; + dp3.namespace = &ns2; + dp3.value.text.str = "Hello"; + dp3.value.text.length = 5; + dp3.vtype = WS_VALUE_TEXT; + + // init test data + p1.namespace = webdav_dav_namespace(); + p1.lang = NULL; + p1.name = "test1"; + p1.value.data = NULL; + p1.vtype = 0; + + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_ASSERT(!r->plist_begin && !r->plist_end, "plist not empty"); + + r->resource.addproperty((WebdavResource*)r, &p1, 200); + UCX_TEST_ASSERT(r->plist_begin, "!plist_begin"); + UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end"); + + r->resource.addproperty((WebdavResource*)r, &p[0], 404); + r->resource.addproperty((WebdavResource*)r, &p[1], 404); + r->resource.addproperty((WebdavResource*)r, &p[2], 403); + r->resource.addproperty((WebdavResource*)r, &p[3], 403); + r->resource.addproperty((WebdavResource*)r, &p[4], 403); + r->resource.addproperty((WebdavResource*)r, &p[5], 403); + r->resource.addproperty((WebdavResource*)r, &p[6], 500); + + UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end"); + + UCX_TEST_ASSERT(r->errors, "no prop errors"); + UCX_TEST_ASSERT(r->errors->next, "no second error code"); + UCX_TEST_ASSERT(r->errors->next->next, "no third error code"); + UCX_TEST_ASSERT(!r->errors->next->next->next, "too many error codes"); + + UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 2, "404 list size != 2"); + UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->begin) == 4, "403 list size != 4"); + UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->next->begin) == 1, "500 list size != 1"); + + // new resource for prop duplication tests + r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/test"); + UCX_TEST_ASSERT(r, "cannot create second response"); + + r->resource.addproperty((WebdavResource*)r, &dp1, 200); + UCX_TEST_ASSERT(r->plist_begin, "adding dp1 failed"); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: list size not 1"); + + r->resource.addproperty((WebdavResource*)r, &dp2, 200); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 should not work"); + + r->resource.addproperty((WebdavResource*)r, &dp2, 404); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 with different status should not work (1)"); + if(r->errors) { + UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 0, "dp1: error list not empty"); + } + + r->resource.addproperty((WebdavResource*)r, &dp3, 200); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp3 should not work"); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_propfind_init) { + reset_backends(); + + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + UcxList *requests = NULL; + int err = webdav_propfind_init(&backend1, propfind, "/", &requests); + + UCX_TEST_ASSERT(!err, "webdav_propfind_init failed"); + UCX_TEST_ASSERT(requests, "request list is empty"); + UCX_TEST_ASSERT(ucx_list_size(requests), "request list has wrong size"); + + WebdavPropfindRequest *p1 = requests->data; + WebdavPropfindRequest *p2 = requests->next->data; + + // backend1 removes the first property from the plist + // backend2 should have one property less + + UCX_TEST_ASSERT(p1 && p2, "missing requests objects"); + UCX_TEST_ASSERT(p1 != p2, "request objects equal"); + UCX_TEST_ASSERT(p1->properties != p2->properties, "plists equal"); + UCX_TEST_ASSERT(p1->propcount == p2->propcount + 1, "first property not removed"); + + UCX_TEST_ASSERT(backend1_init_called == 1, "backend1 init not called"); + UCX_TEST_ASSERT(backend2_init_called == 1, "backend2 init not called"); + + UCX_TEST_END; + + pool_destroy(sn->pool); +} + +UCX_TEST(test_webdav_op_propfind_begin) { + reset_backends(); + + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "WebdavOperation not created"); + + int err = webdav_op_propfind_begin(op, "/", NULL, NULL); + UCX_TEST_ASSERT(err == 0, "err not 0"); + UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend2 propfind_do not called"); + + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_op_propfind_children) { + reset_backends(); + + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "WebdavOperation not created"); + + int err = webdav_op_propfind_begin(op, "/", NULL, NULL); + UCX_TEST_ASSERT(err == 0, "propfind_begin error"); + + // create test vfs with some files (code from test_vfs_readdir) + rq->vfs = testvfs_create(sn); + VFSContext *vfs = vfs_request_context(sn, rq); + UCX_TEST_ASSERT(vfs, "no vfs"); + + err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + // add some test file to /dir + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "dir not opened"); + + UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend1 propfind_do not called") + + // propfind for all children + err = webdav_op_propfind_children(op, vfs, "/", "/dir"); + UCX_TEST_ASSERT(err == 0, "webdav_op_propfind_children failed"); + + // 1 dir + 4 children + UCX_TEST_ASSERT(backend1_propfind_do_count == 5, "backend1 propfind_do wrong count"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 5, "backend2 propfind_do wrong count"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +static void init_test_webdav_method( + Session **out_sn, + Request **out_rq, + TestIOStream **out_st, + pblock **out_pb, + const char *method, + const char *request_body) +{ + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + sn = testutil_session(); + rq = testutil_request(sn->pool, method, "/"); + + pblock_nvinsert("path", "/", rq->vars); + pblock_nvinsert("uri", "/", rq->reqpb); + + st = testutil_iostream(2048, TRUE); + sn->csd = (IOStream*)st; + + if(request_body) { + testutil_request_body(sn, rq, request_body, strlen(request_body)); + } + + pb = pblock_create_pool(sn->pool, 4); + + *out_sn = sn; + *out_rq = rq; + *out_st = st; + *out_pb = pb; +} + +UCX_TEST(test_webdav_propfind) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + int ret; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", TEST_PROPFIND1); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed"); + + xmlDoc *doc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "propfind1: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + testutil_destroy_session(sn); + xmlFreeDoc(doc); + testutil_iostream_destroy(st); + + // Test2 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", TEST_PROPFIND2); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + xmlDoc *doc2 = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "propfind2: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + testutil_destroy_session(sn); + xmlFreeDoc(doc2); + testutil_iostream_destroy(st); + + UCX_TEST_END; + +} + +/* ------------------------------------------------------------------------- + * + * PROPPATCH TESTS + * + * ------------------------------------------------------------------------ */ + +static int test_proppatch_init( + Session **out_sn, + Request **out_rq, + WebdavProppatchRequest **out_proppatch, + const char *xml) +{ + if(!webdav_is_initialized) { + if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) { + return 1; + } + webdav_is_initialized = 1; + } + + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPPATCH", "/"); + + int error = 0; + + WebdavProppatchRequest *proppatch = proppatch_parse( + sn, + rq, + xml, + strlen(xml), + &error); + + if(error) { + return 1; + } + + if(!proppatch || !(proppatch->set || proppatch->remove)) { + return 1; + } + + *out_sn = sn; + *out_rq = rq; + *out_proppatch = proppatch; + return 0; +} + +static WebdavOperation* test_proppatch_op1( + Session **out_sn, + Request **out_rq, + const char *xml) +{ + WebdavProppatchRequest *proppatch; + if(test_proppatch_init(out_sn, out_rq, &proppatch, xml)) { + return NULL; + } + + Multistatus *ms = multistatus_response(*out_sn, *out_rq); + if(!ms) { + return NULL; + } + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + return webdav_create_proppatch_operation( + (*out_sn), + (*out_rq), + &backend1, + proppatch, + response); +} + + +UCX_TEST(test_proppatch_msresponse) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 200), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + MSResponse *msres = (MSResponse*)res; + UCX_TEST_ASSERT(!msres->errors, "error list not NULL"); + UCX_TEST_ASSERT(msres->plist_begin, "elm1 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next, "elm2 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next, "elm3 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next->next, "elm4 missing"); + UCX_TEST_ASSERT(!msres->plist_begin->next->next->next->next, "count != 4"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_msresponse_addproperty_with_errors) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 409), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + // all properties should have an error status code now + // 1 x 409, 3 x 424 + + MSResponse *msres = (MSResponse*)res; + + UCX_TEST_ASSERT(!msres->plist_begin, "plist not NULL"); + UCX_TEST_ASSERT(msres->errors, "error list is NULL"); + UCX_TEST_ASSERT(msres->errors->next, "second error list is missing"); + UCX_TEST_ASSERT(!msres->errors->next->next, "wrong error list size"); + + // We know that we have 2 error lists, one with status code 409 and + // the other must have 409. However we don't enforce the order of the + // error lists, therefore check both variants + if(msres->errors->status == 409) { + UCX_TEST_ASSERT(msres->errors->next->status == 424, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 409 property"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 424 properties"); + } else { + UCX_TEST_ASSERT(msres->errors->next->status == 409, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 424 properties"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 409 property"); + } + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_op_proppatch) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + // TEST_PROPPATCH2 should succeed + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + int ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed"); + UCX_TEST_ASSERT(backend1_proppatch_commit, "backend1 no commit"); + UCX_TEST_ASSERT(backend2_proppatch_commit, "backend2 no commit"); + UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (1)"); + UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (1)"); + UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (1)"); + UCX_TEST_ASSERT(backend2_proppatch_finish_count == 1, "backend1 wrong finish count (1)"); + + // TEST_PROPPATCH3 should fail (commit == FALSE) + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH3); + UCX_TEST_ASSERT(op, "failed to create proppatch operation 2"); + + ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed (2)"); + UCX_TEST_ASSERT(!backend1_proppatch_commit, "backend1 commit"); + UCX_TEST_ASSERT(!backend2_proppatch_commit, "backend2 commit"); + + // TEST_PROPPATCH4 should abort + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH4); + UCX_TEST_ASSERT(op, "failed to create proppatch operation 3"); + + ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret != 0, "webdav_op_proppatch should fail"); + UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (2)"); + UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (2)"); + UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (2)"); + UCX_TEST_ASSERT(backend2_proppatch_finish_count == 0, "backend1 wrong finish count (2)"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +#define xstreq(a, b) (!strcmp((const char*)a, (const char*)b)) + +UCX_TEST(test_webdav_proppatch) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + int ret; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", TEST_PROPPATCH2); + rq->davCollection = &backend1; + ret = webdav_proppatch(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_proppatch (1) failed"); + + xmlDoc *doc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "proppatch1: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + xmlNode *root = xmlDocGetRootElement(doc); + UCX_TEST_ASSERT(root, "proppatch1: no root"); + + xmlNode *nodeC = NULL; + xmlNode *node = root->children; + int depth = 1; + while(node) { + const xmlChar *name = node->name; + int nextNode = 1; + if(node->type != XML_ELEMENT_NODE) { + // nothing + } else if(depth == 1) { + if(xstreq(name, "response")) { + nextNode = 0; + } + } else if(depth == 2) { + if(xstreq(name, "propstat")) { + nextNode = 0; + } + } else if(depth == 3) { + if(xstreq(name, "prop")) { + nextNode = 0; + } + } else if(depth == 4) { + if(xstreq(name, "c")) { + nodeC = node; + break; + } + } + + if(nextNode) { + node = node->next; + } else { + node = node->children; + depth++; + } + } + + UCX_TEST_ASSERT(nodeC, "prop c not in response"); + UCX_TEST_ASSERT(!nodeC->children, "properties must not have a value"); + + testutil_destroy_session(sn); + xmlFreeDoc(doc); + testutil_iostream_destroy(st); + + + UCX_TEST_END; +} + + +/* ------------------------------------------------------------------------- + * + * WEBDAV VFS TESTS + * + * ------------------------------------------------------------------------ */ + +static int mkcol_data1 = 10; +static int mkcol_data2 = 20; +static int mkcol_data3 = 30; +static int mkcol_data4 = 40; + +static int mkcol_count = 0; +static int mkcol_finish_count = 0; + +static int mkcol_err = 0; + +static int set_created = 0; + +static int test_webdav_mkcol(WebdavVFSRequest *req, WSBool *created) { + mkcol_count++; + + switch(mkcol_count) { + case 1: { + req->userdata = &mkcol_data1; + break; + } + case 2: { + req->userdata = &mkcol_data2; + break; + } + case 3: { + req->userdata = &mkcol_data3; + break; + } + case 4: { + req->userdata = &mkcol_data4; + break; + } + default: break; + } + + if(set_created) { + *created = TRUE; + set_created = 0; + } + + return 0; +} + +static int test_webdav_mkcol_finish(WebdavVFSRequest *req, WSBool success) { + mkcol_finish_count++; + + if(mkcol_finish_count == 1) { + int *data = req->userdata; + if(data != &mkcol_data1) { + mkcol_err = 1; + } + } else if(mkcol_finish_count == 3) { + int *data = req->userdata; + if(data != &mkcol_data3) { + mkcol_err = 1; + } + } else { + int *data = req->userdata; + // data4 should never be used + if(data == &mkcol_data4) { + mkcol_err = 1; + } + } + + return 0; +} + +static int test_webdav_mkcol_fail(WebdavVFSRequest *req, WSBool *created) { + mkcol_count++; + return 1; +} + +static int delete_count = 0; +static int delete_finish_count = 0; + +static int test_backend_webdav_delete(WebdavVFSRequest *req, WSBool *created) { + delete_count++; + return 0; +} + +static int test_backend_webdav_delete_finish(WebdavVFSRequest *req, WSBool success) { + delete_finish_count++; + return 0; +} + + +UCX_TEST(test_webdav_vfs_op_do) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + // Tests performed primarily with MKCOL, because webdav_vfs_op_do + // behaves the same for both operations + // the only difference are the callbacks + + init_test_webdav_method(&sn, &rq, &st, &pb, "MKCOL", NULL); + VFS *testvfs = testvfs_create(sn); + rq->vfs = testvfs; + + WebdavBackend dav1; + ZERO(&dav1, sizeof(WebdavBackend)); + dav1.opt_mkcol = test_webdav_mkcol; + dav1.opt_mkcol_finish = test_webdav_mkcol_finish; + dav1.opt_delete = test_backend_webdav_delete; + dav1.opt_delete_finish = test_backend_webdav_delete_finish; + + WebdavBackend dav2; + ZERO(&dav2, sizeof(WebdavBackend)); + dav2.opt_mkcol_finish = test_webdav_mkcol_finish; + + WebdavBackend dav3; + ZERO(&dav3, sizeof(WebdavBackend)); + dav3.opt_mkcol = test_webdav_mkcol; + + WebdavBackend dav4; + ZERO(&dav4, sizeof(WebdavBackend)); + dav4.opt_mkcol = test_webdav_mkcol; + dav4.opt_mkcol_finish = test_webdav_mkcol_finish; + + dav1.next = &dav2; + dav2.next = &dav3; + dav3.next = &dav4; + + rq->davCollection = &dav1; + + UCX_TEST_BEGIN; + + WebdavVFSOperation *op1 = webdav_vfs_op(sn, rq, &dav1, FALSE); + + int ret = webdav_vfs_op_do(op1, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(!ret, "webdav_vfs_op_do failed"); + UCX_TEST_ASSERT(mkcol_count == 3, "wrong mkcol_count"); + UCX_TEST_ASSERT(mkcol_finish_count == 3, "wrong mkcol_finish_count"); + UCX_TEST_ASSERT(mkcol_err == 0, "mkcol_err"); + + // test without VFS, but set *created to TRUE to skip VFS usage + rq->vfs = NULL; + set_created = 1; + + WebdavVFSOperation *op2 = webdav_vfs_op(sn, rq, &dav1, FALSE); + ret = webdav_vfs_op_do(op2, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(!ret, "op2 failed"); + + // test 3: abort after first backend + mkcol_count = 0; + mkcol_finish_count = 0; + dav1.opt_mkcol = test_webdav_mkcol_fail; + + WebdavVFSOperation *op3 = webdav_vfs_op(sn, rq, &dav1, FALSE); + ret = webdav_vfs_op_do(op3, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(ret, "op3 should fail"); + UCX_TEST_ASSERT(mkcol_count == 1, "op3: wrong mkcol_count"); + UCX_TEST_ASSERT(mkcol_finish_count == 1, "op3: wrong mkcol_finish_count"); + + // test DELETE to make sure, delete callbacks will be used + pblock_replace("path", "/deltest", rq->vars); + rq->vfs = testvfs; + WebdavVFSOperation *op_del = webdav_vfs_op(sn, rq, &dav1, FALSE); + vfs_open(op_del->vfs, "/deltest", O_CREAT); + ret = webdav_vfs_op_do(op_del, WEBDAV_VFS_DELETE); + + UCX_TEST_ASSERT(!ret, "op_del failed"); + UCX_TEST_ASSERT(delete_count == 1, "op_del: wrong delete_count"); + UCX_TEST_ASSERT(delete_finish_count == 1, "op_del: wrong delete_finish_count"); + + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_delete){ + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + init_test_webdav_method(&sn, &rq, &st, &pb, "DELETE", NULL); + rq->vfs = testvfs_create(sn); + + WebdavBackend dav1; + ZERO(&dav1, sizeof(WebdavBackend)); + dav1.opt_delete = test_backend_webdav_delete; + dav1.opt_delete_finish = test_backend_webdav_delete_finish; + delete_count = 0; + delete_finish_count = 0; + rq->davCollection = &dav1; + + UCX_TEST_BEGIN; + + // prepare + VFSContext *vfs = vfs_request_context(sn, rq); + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir dir1 failed"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir dir2 failed"); + err = vfs_mkdir(vfs, "/dir2/dir3"); + UCX_TEST_ASSERT(err == 0, "mkdir dir3 failed"); + err = vfs_mkdir(vfs, "/dir2/dir4"); + UCX_TEST_ASSERT(err == 0, "mkdir dir4 failed"); + err = vfs_mkdir(vfs, "/dir2/dir4/dir5"); + UCX_TEST_ASSERT(err == 0, "mkdir dir5 failed"); + + SYS_FILE f0 = vfs_open(vfs, "/file0", O_CREAT); + UCX_TEST_ASSERT(f0, "f0 create failed"); + // no f1 + SYS_FILE f2 = vfs_open(vfs, "/dir2/file2", O_CREAT); + UCX_TEST_ASSERT(f2, "f2 create failed"); + SYS_FILE f3 = vfs_open(vfs, "/dir2/dir3/file3", O_CREAT); + UCX_TEST_ASSERT(f3, "f3 create failed"); + SYS_FILE f4 = vfs_open(vfs, "/dir2/dir4/file4", O_CREAT); + UCX_TEST_ASSERT(f4, "f4 create failed"); + SYS_FILE f5 = vfs_open(vfs, "/dir2/dir4/dir5/file5", O_CREAT); + UCX_TEST_ASSERT(f5, "f5 create failed"); + + // delete single file + pblock_replace("path", "/file0", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /file0 failed"); + UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count"); + + delete_count = 0; + pblock_replace("path", "/dir1", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /dir1 failed"); + UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count"); + + delete_count = 0; + pblock_replace("path", "/dir2", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /dir2 failed"); + UCX_TEST_ASSERT(delete_count == 8, "del2: wrong delete count"); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_put) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + const char *content_const = "Hello World"; + + init_test_webdav_method(&sn, &rq, &st, &pb, "PUT", content_const); + rq->vfs = testvfs_create(sn); + + UCX_TEST_BEGIN; + + int err; + + pblock_replace("path", "/file0", rq->vars); + err = webdav_put(NULL, sn, rq); + + UCX_TEST_ASSERT(err == REQ_PROCEED, "put failed"); + + VFSContext *vfs = vfs_request_context(sn, rq); + SYS_FILE f0 = vfs_open(vfs, "/file0", 0); + UCX_TEST_ASSERT(f0, "cannot open file0"); + + char buf[1024]; + int r = system_fread(f0, buf, 1024); + + UCX_TEST_ASSERT(r == strlen(content_const), "wrong file size"); + UCX_TEST_ASSERT(!memcmp(content_const, buf, r), "wrong file content"); + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/webdav.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,192 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_WEBDAV_H +#define TEST_WEBDAV_H + +#include "../public/nsapi.h" +#include "../public/webdav.h" + +#include <ucx/test.h> + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_webdav_plist_add); +UCX_TEST(test_webdav_plist_size); + +UCX_TEST(test_propfind_parse); +UCX_TEST(test_proppatch_parse); +UCX_TEST(test_lock_parse); + +UCX_TEST(test_rqbody2buffer); + +UCX_TEST(test_webdav_plist_iterator); +UCX_TEST(test_webdav_plist_iterator_remove_current); + +UCX_TEST(test_msresponse_addproperty); +UCX_TEST(test_msresponse_addproperty_with_errors); + +UCX_TEST(test_webdav_propfind_init); +UCX_TEST(test_webdav_op_propfind_begin); +UCX_TEST(test_webdav_op_propfind_children); + +UCX_TEST(test_webdav_propfind); + +UCX_TEST(test_proppatch_msresponse); +UCX_TEST(test_webdav_op_proppatch); + +UCX_TEST(test_webdav_proppatch); + +UCX_TEST(test_webdav_vfs_op_do); + +UCX_TEST(test_webdav_delete); +UCX_TEST(test_webdav_put); + +/* --------------------------- PROPFIND --------------------------- */ + +#define TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:prop> \ + <D:displayname/> \ + <D:getcontentlength/> \ + <D:getcontenttype/> \ + <D:getlastmodified/> \ + <D:resourcetype/> \ + <D:getetag/> \ + </D:prop> \ + </D:propfind>" + +#define TEST_PROPFIND2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:prop xmlns:X=\"http://example.com/\"> \ + <D:resourcetype/> \ + <X:testprop/> \ + <X:name/> \ + <Z:testprop xmlns:Z=\"testns\"/>\ + </D:prop> \ + </D:propfind>" + +#define TEST_PROPFIND3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:allprop/> \ + </D:propfind>" + +#define TEST_PROPFIND4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:propname/> \ + </D:propfind>" + +#define TEST_PROPFIND5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:prop> \ + <D:displayname/> \ + <D:getcontentlength/> \ + <D:getetag/> \ + <D:getcontentlength/> \ + <D:resourcetype/> \ + </D:prop> \ + </D:propfind>" + +#define TEST_PROPFIND6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\"> \ + <D:prop> \ + <D:displayname/> \ + <D:getcontentlength/> \ + <D:getetag/> \ + <D:resourcetype/> \ + </D:prop> \ + <D:allprop/> \ + </D:propfind>" + +/* --------------------------- PROPPATCH --------------------------- */ + +#define TEST_PROPPATCH1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \ + <D:set> \ + <D:prop><X:test>test</X:test><D:creationdate>123</D:creationdate></D:prop> \ + </D:set> \ + </D:propertyupdate>" + +#define TEST_PROPPATCH2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \ + <D:set> \ + <D:prop> \ + <X:a>test</X:a> \ + <X:b>15</X:b> \ + <X:c> \ + <X:name test='test1' abc='def'>User</X:name><X:mail>user@host</X:mail> \ + </X:c> \ + <X:d><X:name>Test</X:name></X:d> \ + </D:prop> \ + </D:set> \ + <D:remove> \ + <D:prop> \ + <X:e/> \ + </D:prop> \ + </D:remove> \ + </D:propertyupdate>" + +#define TEST_PROPPATCH3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \ + <D:set> \ + <D:prop> \ + <X:a>test</X:a> \ + <X:fail>15</X:fail> \ + </D:prop> \ + </D:set> \ + <D:remove> \ + <D:prop> \ + <X:e/> \ + </D:prop> \ + </D:remove> \ + </D:propertyupdate>" + +#define TEST_PROPPATCH4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propertyupdate xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\"> \ + <D:set> \ + <D:prop><X:abort>error</X:abort></D:prop> \ + </D:set> \ + </D:propertyupdate>" + +/* --------------------------- LOCK --------------------------- */ + +#define TEST_LOCK1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:lockinfo xmlns:D=\"DAV:\"> \ + <D:lockscope><D:shared/></D:lockscope> \ + <D:locktype>D:write/></D:locktype> \ + <D:owner>User</D:owner> \ + </D:lockinfo>" + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_WEBDAV_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/writer.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,151 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "../util/writer.h" + +#include <ucx/buffer.h> + +#include "writer.h" +#include "testutils.h" + +UCX_TEST(test_writer_putc) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 4); + Writer *out = &writer; + + writer_putc(out, 'a'); + UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos"); + + writer_putc(out, 'b'); + UCX_TEST_ASSERT(wbuf[1] == 'b', "2: wrong char at pos 1"); + UCX_TEST_ASSERT(writer.pos == 2, "2: wrong pos"); + + writer_putc(out, 'c'); + writer_putc(out, 'd'); + UCX_TEST_ASSERT(wbuf[2] == 'c', "3: wrong char at pos 2"); + UCX_TEST_ASSERT(wbuf[3] == 'd', "4: wrong char at pos 3"); + + writer_putc(out, 'f'); // should flush the buffer + UCX_TEST_ASSERT(wbuf[0] == 'f', "5: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "5: wrong pos"); + UCX_TEST_ASSERT(buf->space[0] == 'a', "5: wrong char at UcxBuffer pos 0"); + UCX_TEST_ASSERT(buf->space[1] == 'b', "5: wrong char at UcxBuffer pos 1"); + UCX_TEST_ASSERT(buf->pos == 4, "5: wrong UcxBuffer pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} + +UCX_TEST(test_writer_flush) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 4); + Writer *out = &writer; + + writer_putc(out, 'a'); + UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos"); + + writer_flush(out); + UCX_TEST_ASSERT(writer.pos == 0, "wrong pos after flush"); + UCX_TEST_ASSERT(buf->space[0] == 'a', "wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 1, "wrong UcxBuffer pos"); + + writer_putc(out, 'b'); + UCX_TEST_ASSERT(wbuf[0] == 'b', "2: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "2: wrong pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} + +UCX_TEST(test_writer_put) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 8); + Writer *out = &writer; + + writer_put(out, "abcd", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "abcd", 4), "1: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "1: wrong pos"); + + writer_put(out, "efgh", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "abcdefgh", 8), "2: wrong content"); + UCX_TEST_ASSERT(writer.pos == 8, "2: wrong pos"); + + writer_put(out, "1234", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "1234", 4), "3: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "3: wrong pos"); + UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh", 8), "3: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 8, "3: wrong UcxBuffer pos"); + + writer_put(out, "5678xx", 6); + UCX_TEST_ASSERT(!memcmp(wbuf, "xx", 2), "4: wrong content"); + UCX_TEST_ASSERT(writer.pos == 2, "4: wrong pos"); + UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh12345678", 16), "4: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 16, "4: wrong UcxBuffer pos"); + + writer_puts(out, S("345678abcdefgh12345678end.")); + UCX_TEST_ASSERT(!memcmp(wbuf, "end.", 4), "5: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "5: wrong pos"); + UCX_TEST_ASSERT(!memcmp( + buf->space, + "abcdefgh12345678xx345678abcdefgh12345678", + 40), + "5: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 40, "5: wrong UcxBuffer pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/writer.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_WRITER_H +#define TEST_WRITER_H + +#include <ucx/test.h> + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_writer_putc); +UCX_TEST(test_writer_flush); +UCX_TEST(test_writer_put); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_WRITER_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/xml.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,337 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xml.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <libxml/tree.h> + +#include "testutils.h" + +#include "../public/webdav.h" +#include "../util/writer.h" + +#include "../webdav/webdav.h" +#include "../webdav/xml.h" + +typedef struct Test1Data { + int beginCounter; + int endCounter; + int elmCounter; + int endElmCounter; + int textErr; + int err; + int endErr; + int nodesWithAttributesCounter; + xmlNode *prev; +} Test1Data; + +static int test1_begin(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->beginCounter++; + + if(node->type == XML_ELEMENT_NODE) { + data->elmCounter++; + const char *name = (const char*)node->name; + + if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) { + data->err = 1; + return 1; + } + + switch(data->elmCounter) { + case 1: if(strcmp(name, "test")){ data->err = 1; return 1; } break; + case 2: if(strcmp(name, "elm1")) { data->err = 1; return 1; } break; + case 3: if(strcmp(name, "elm2")) { data->err = 1; return 1; } break; + case 4: if(strcmp(name, "c")) { data->err = 1; return 1; } break; + case 5: if(strcmp(name, "a")) { data->err = 1; return 1; } break; + case 6: if(strcmp(name, "d")) { data->err = 1; return 1; } break; + case 7: if(strcmp(name, "e")) { data->err = 1; return 1; } break; + case 8: if(strcmp(name, "b")) { data->err = 1; return 1; } break; + case 9: if(strcmp(name, "x")) { data->err = 1; return 1; } break; + case 10: if(strcmp(name, "z")) { data->err = 1; return 1; } break; + case 11: if(strcmp(name, "nextelm")) { data->err = 1; return 1; } break; + } + } else if(node->type == XML_TEXT_NODE) { + const char *text = (const char*)node->content; + if(!strcmp(text, "teststr")) { + if(strcmp((const char*)data->prev->name, "elm1")) { + data->textErr = 1; + return 1; + } + } else if(!strcmp(text, "hello") || !strcmp(text, "world")) { + if(strcmp((const char*)data->prev->name, "a")) { + data->textErr = 1; + return 1; + } + } + } + + if(node->type == XML_ELEMENT_NODE) { + data->prev = node; + } + return 0; +} + +static int test1_end(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->endCounter++; + + if(node->type == XML_ELEMENT_NODE) { + data->endElmCounter++; + const char *name = (const char*)node->name; + + if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) { + data->err = 1; + return 1; + } + + switch(data->endElmCounter) { + case 1: if(strcmp(name, "elm1")){ data->endErr = 1; return 1; } break; + case 2: if(strcmp(name, "elm2")){ data->endErr = 1; return 1; } break; + case 3: if(strcmp(name, "a")){ data->endErr = 1; return 1; } break; + case 4: if(strcmp(name, "b")){ data->endErr = 1; return 1; } break; + case 5: if(strcmp(name, "e")){ data->endErr = 1; return 1; } break; + case 6: if(strcmp(name, "d")){ data->endErr = 1; return 1; } break; + case 7: if(strcmp(name, "c")){ data->endErr = 1; return 1; } break; + case 8: if(strcmp(name, "z")){ data->endErr = 1; return 1; } break; + case 9: if(strcmp(name, "x")){ data->endErr = 1; return 1; } break; + case 10: if(strcmp(name, "test")){ data->endErr = 1; return 1; } break; + case 11: if(strcmp(name, "nextelm")) { data->endErr = 1; return 1; } break; + } + } + + return 0; +} + +static int test2_begin(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->beginCounter++; + if(node->type == XML_ELEMENT_NODE) { + data->elmCounter++; + if(node->properties) { + data->nodesWithAttributesCounter++; + } + } + return 0; +} + +static int test2_end(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->endCounter++; + if(node->type == XML_ELEMENT_NODE) { + data->endElmCounter++; + } + return 0; +} + +UCX_TEST(test_wsxml_iterator) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + xmlDoc *doc = xmlReadMemory( + XML_TESTDATA1, strlen(XML_TESTDATA1), NULL, NULL, 0); + xmlDoc *doc2 = xmlReadMemory( + XML_TESTDATA2, strlen(XML_TESTDATA2), NULL, NULL, 0); + xmlDoc *doc6 = xmlReadMemory( + XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "doc is NULL"); + UCX_TEST_ASSERT(doc2, "doc2 is NULL"); + UCX_TEST_ASSERT(doc6, "doc6 is NULL"); + + xmlNode *root = xmlDocGetRootElement(doc); + + // Test 1: iterate over complete document + Test1Data testdata; + ZERO(&testdata, sizeof(Test1Data)); + int ret = wsxml_iterator(sn->pool, root, test1_begin, test1_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "wsxml_iterator failed"); + UCX_TEST_ASSERT(!testdata.err, "wrong element order (begin)"); + UCX_TEST_ASSERT(!testdata.endErr, "wrong element order (end)"); + UCX_TEST_ASSERT(!testdata.textErr, "text order error"); + UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "begin/end counter not equal"); + + // Test 2: iterate over sub-document + ZERO(&testdata, sizeof(Test1Data)); + xmlNode *root2 = xmlDocGetRootElement(doc2); + xmlNode *sub = root2->children->children; + ret = wsxml_iterator(sn->pool, sub, test1_begin, test1_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "test2: wsxml_iterator failed"); + UCX_TEST_ASSERT(!testdata.err, "test2: wrong element order (begin)"); + UCX_TEST_ASSERT(!testdata.endErr, "test2: wrong element order (end)"); + UCX_TEST_ASSERT(!testdata.textErr, "test2: text order error"); + UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "test2: begin/end counter not equal"); + + // Test 3: iterate over document with all kinds of node types + xmlNode *root6 = xmlDocGetRootElement(doc6); + ZERO(&testdata, sizeof(Test1Data)); + ret = wsxml_iterator(sn->pool, root6, test2_begin, test2_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "test3: wsxml_iterator failed"); + UCX_TEST_ASSERT(testdata.elmCounter == testdata.endElmCounter, "test3: begin/end counter not equal"); + UCX_TEST_ASSERT(testdata.elmCounter == 12, "test3: wrong elm counter"); + UCX_TEST_ASSERT(testdata.nodesWithAttributesCounter == 5, "test3: wrong entity ref counter"); + + xmlFreeDoc(doc); + xmlFreeDoc(doc2); + xmlFreeDoc(doc6); + UCX_TEST_END; +} + +// checks if the namespace list contains the test namespaces x1, x2, x3 and x4 +static void check_ns_list(WebdavNSList *list, int *x1, int *x2, int *x3, int *x4) { + *x1 = 0; + *x2 = 0; + *x3 = 0; + *x4 = 0; + + WebdavNSList *elm = list; + while(elm) { + if(!strcmp((const char*)elm->namespace->prefix, "x1") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns1/")) + { + *x1 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x2") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns2/")) + { + *x2 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x3") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/")) + { + *x3 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x4") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/")) + { + *x4 = 1; + } + + elm = elm->next; + } +} + +UCX_TEST(test_wsxml_get_required_namespaces) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + xmlDoc *doc3 = xmlReadMemory( + XML_TESTDATA3, strlen(XML_TESTDATA3), NULL, NULL, 0); + xmlDoc *doc4 = xmlReadMemory( + XML_TESTDATA4, strlen(XML_TESTDATA4), NULL, NULL, 0); + xmlDoc *doc5 = xmlReadMemory( + XML_TESTDATA5, strlen(XML_TESTDATA5), NULL, NULL, 0); + + xmlNode *node0 = xmlDocGetRootElement(doc3); + xmlNode *node1 = xmlDocGetRootElement(doc3)->children; + xmlNode *node2 = xmlDocGetRootElement(doc4)->children; + xmlNode *node3 = xmlDocGetRootElement(doc5)->children; + + UCX_TEST_ASSERT(doc3, "doc3 is NULL"); + UCX_TEST_ASSERT(doc4, "doc4 is NULL"); + UCX_TEST_ASSERT(doc5, "doc5 is NULL"); + + int err0, err1, err2, err3; + int x1 = 0; + int x2 = 0; + int x3 = 0; + int x4 = 0; + WebdavNSList *elm = NULL; + + // Test 0: + WebdavNSList *ns0 = wsxml_get_required_namespaces(sn->pool, node0, &err0); + UCX_TEST_ASSERT(!err0, "ns0 failed"); + UCX_TEST_ASSERT(!ns0, "ns0: nsdefs should be ignored"); + + WebdavNSList *ns1 = wsxml_get_required_namespaces(sn->pool, node1, &err1); + check_ns_list(ns1, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err1, "ns1 failed"); + UCX_TEST_ASSERT(ns1, "ns1: no list"); + UCX_TEST_ASSERT(x1, "ns1: x1 missing"); + UCX_TEST_ASSERT(x2, "ns1: x2 missing"); + UCX_TEST_ASSERT(x3, "ns1: x3 missing"); + UCX_TEST_ASSERT(x4, "ns1: x4 missing"); + + WebdavNSList *ns2 = wsxml_get_required_namespaces(sn->pool, node2, &err2); + check_ns_list(ns2, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err2, "ns2 failed"); + UCX_TEST_ASSERT(ns2, "ns2: no list"); + UCX_TEST_ASSERT(x1, "ns2: x1 missing"); + UCX_TEST_ASSERT(x2, "ns2: x2 missing"); + UCX_TEST_ASSERT(!x3, "ns2: x3"); + UCX_TEST_ASSERT(!x4, "ns2: x4"); + + WebdavNSList *ns3 = wsxml_get_required_namespaces(sn->pool, node3, &err3); + check_ns_list(ns3, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err3, "ns3 failed"); + UCX_TEST_ASSERT(ns3, "ns3: no list"); + UCX_TEST_ASSERT(x1, "ns3: x1 missing"); + UCX_TEST_ASSERT(x2, "ns3: x2 missing"); + UCX_TEST_ASSERT(!x3, "ns3: x3"); + UCX_TEST_ASSERT(!x4, "ns3: x4"); + + xmlFreeDoc(doc3); + xmlFreeDoc(doc4); + xmlFreeDoc(doc5); + UCX_TEST_END; +} + +UCX_TEST(test_wsxml_write_nodes) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + + UCX_TEST_BEGIN; + xmlDoc *doc = xmlReadMemory( + XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "xml parser error"); + xmlNode *root = xmlDocGetRootElement(doc); + + Writer writer; + char buffer[1024]; + writer_init(&writer, st, buffer, 1024); + + int err = wsxml_write_nodes(sn->pool, &writer, NULL, root); + writer_flush(&writer); + UCX_TEST_ASSERT(err == 0, "wsxml_write_nodes error"); + UCX_TEST_ASSERT(st->buf->pos > 0, "buffer is empty"); + + //printf("\n\n"); + //printf("%.*s\n", (int)st->buf->size, st->buf->space); + //printf("\n\n"); + + xmlDoc *genDoc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(genDoc, "generated doc is not valid xml"); + + xmlFreeDoc(doc); + xmlFreeDoc(genDoc); + + UCX_TEST_END; + testutil_iostream_destroy(st); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/xml.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_XML_H +#define TEST_XML_H + +#include "../public/nsapi.h" +#include <ucx/test.h> + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_wsxml_iterator); +UCX_TEST(test_wsxml_get_required_namespaces); +UCX_TEST(test_wsxml_write_nodes); + + +#define XML_TESTDATA1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <test> \ + <elm1>teststr</elm1> \ + <!-- comment -->\ + <elm2 /> \ + <c> \ + <a>hello</a> \ + world\ + <d><e><b/></e></d>\ + </c> \ + <x><z/></x> \ + </test>" + +#define XML_TESTDATA2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <root><wrapper><test> \ + <elm1>teststr</elm1> \ + <!-- comment -->\ + <elm2 /> \ + <c> \ + <a>hello</a> \ + world\ + <d><e><b/></e></d>\ + </c> \ + <x><z/></x> \ + </test><nextelm/></wrapper><ignore/></root>" + +#define XML_TESTDATA3 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <x1:prop \ + xmlns:x1=\"http://example.com/ns1/\" \ + xmlns:x2=\"http://example.com/ns2/\" \ + xmlns:x3=\"http://example.com/ns_0/\" \ + xmlns:x4=\"http://example.com/ns_0/\" > \ + <x1:elm1>str1</x1:elm1>\ + <x2:elm2>str1</x2:elm2>\ + <x3:elm3>str1</x3:elm3>\ + <x4:elm4>str1</x4:elm4>\ + </x1:prop>" + +#define XML_TESTDATA4 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <x1:prop \ + xmlns:x1=\"http://example.com/ns1/\" \ + xmlns:x2=\"http://example.com/ns2/\" \ + xmlns:x3=\"http://example.com/ns_0/\" \ + xmlns:x4=\"http://example.com/ns_0/\" >\ + <x1:elm1>str1</x1:elm1>\ + <x2:elm2>str1</x2:elm2>\ + </x1:prop>" + +#define XML_TESTDATA5 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <x1:prop \ + xmlns:x1=\"http://example.com/ns1/\" \ + xmlns:x2=\"http://example.com/ns2/\" > \ + <x1:elm1>str1</x1:elm1>\ + <x2:elm2>str1</x2:elm2>\ + <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\ + <x4:elm4 xmlns:x4=\"http://example.com/ns_0/\" >str1</x4:elm4>\ + </x1:prop>" + +#define XML_TESTDATA6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \n\ + <x1:test \n\ + xmlns:x1=\"http://example.com/ns1/\" \n\ + xmlns:x2=\"http://example.com/ns2/\" > \n\ + <x1:elm1>str1</x1:elm1>\n\ + <x2:elm2>str1</x2:elm2>\n\ + <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\n\ + <x1:sub> \n\ + <x1:a attr1=\"val1\"/> \n\ + <x1:a attr2=\"val2\">text</x1:a>\n\ + <x1:b x2:nsattr=\"nsval\"><x1:c/></x1:b>\n\ + </x1:sub> \n\ + <x1:newns xmlns:x4=\"http://example.com/0/\" x4:attr3=\"val3\">\n\ + </x1:newns>\n\ + <x1:text>Hello\n\ + World\n\ + end.\n\ + </x1:text>\n\ + <x1:entityref ea=\"test & value\">\n\ + entity reference test &quote& \n\ + <xml>\n\ + </x1:entityref>\n\ + </x1:test>\n" + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_XML_H */ +
--- a/src/server/util/io.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/io.c Tue Aug 25 12:07:56 2020 +0200 @@ -269,8 +269,10 @@ io[1].iov_len = nbytes; io[2].iov_base = "\r\n"; io[2].iov_len = 2; + // TODO: FIXME: if r < sum of iov_len, everything would explode + // we need to store the chunk state and remaining bytes ssize_t r = fd->writev(fd, io, 3); - return r - io[0].iov_len; + return r - io[0].iov_len - io[2].iov_len; } else { return fd->write(fd, buf, nbytes); } @@ -280,6 +282,9 @@ IOStream *fd = st->fd; if(st->chunked_enc) { struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec)); + if(!io) { + return 0; + } char chunk_len[16]; io[0].iov_base = chunk_len; size_t len = 0; @@ -289,7 +294,10 @@ io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len); memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec)); ssize_t r = fd->writev(fd, io, iovcnt + 1); - return r - io[0].iov_len; + + ssize_t ret = r - io[0].iov_len; + free(io); + return ret; } else { return fd->writev(fd, iovec, iovcnt); }
--- a/src/server/util/netbuf.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/netbuf.c Tue Aug 25 12:07:56 2020 +0200 @@ -143,7 +143,11 @@ return bytes_in_buffer; } } - + + if(!buf->sd) { + return NETBUF_EOF; + } + /* The netbuf is empty. Read data directly into the caller's buffer */ bytes = net_read(buf->sd, buffer, size); if (bytes == 0)
--- a/src/server/util/objs.mk Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/objs.mk Tue Aug 25 12:07:56 2020 +0200 @@ -42,6 +42,7 @@ UTILOBJ += thrpool.o UTILOBJ += util.o UTILOBJ += date.o +UTILOBJ += writer.o UTILOBJS = $(UTILOBJ:%=$(UTIL_OBJPRE)%) UTILSOURCE = $(UTILOBJ:%.o=util/%.c)
--- a/src/server/util/pblock.cpp Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/pblock.cpp Tue Aug 25 12:07:56 2020 +0200 @@ -324,7 +324,8 @@ const pb_key *const pb_key_vary = _create_key("vary"); const pb_key *const pb_key_via = _create_key("via"); const pb_key *const pb_key_warning = _create_key("warning"); - +const pb_key *const pb_key_depth = _create_key("depth"); +const pb_key *const pb_key_if = _create_key("if"); /* ------------------------------ _find_key ------------------------------- */
--- a/src/server/util/pblock.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/pblock.h Tue Aug 25 12:07:56 2020 +0200 @@ -301,6 +301,8 @@ extern const pb_key *const pb_key_vary; extern const pb_key *const pb_key_via; extern const pb_key *const pb_key_warning; +extern const pb_key *const pb_key_depth; +extern const pb_key *const pb_key_if; NSAPI_PUBLIC pool_handle_t *pblock_pool(pblock *pb);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/platform.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BASE_PLATFORM_H +#define BASE_PLATFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__clang__) +#define WS_FALLTHROUGH [[clang::fallthrough]] +#elif defined(__GNUC__) +#define WS_FALLTHROUGH __attribute__((fallthrough)) +#else +#define WS_FALLTHROUGH +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* BASE_PLATFORM_H */ +
--- a/src/server/util/systems.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/util/systems.h Tue Aug 25 12:07:56 2020 +0200 @@ -58,10 +58,6 @@ typedef int PRBool; #define PR_TRUE 1 #define PR_FALSE 0 - -typedef int WSBool; -#define WS_TRUE 1 -#define WS_FALSE 0 /* end new types */ /* --- End common definitions for all supported platforms --- */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/writer.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,107 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "writer.h" + +void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len) { + w->fd = fd; + w->buffer = buf; + w->size = len; + w->pos = 0; + w->error = 0; +} + +int writer_flush(Writer *w) { + if(w->error) { + return w->error; + } + + size_t pos = 0; + size_t len = w->pos; + + while(len > 0) { + ssize_t r = net_write(w->fd, w->buffer + pos, len); + if(r <= 0) { + break; + } + len -= r; + pos += r; + } + + if(pos != w->pos) { + w->error = 1; + return 1; + } + + w->pos = 0; + return 0; +} + +int writer_put(Writer *w, const char *s, size_t len) { + if(w->error) { + return w->error; + } + + // available bytes + size_t a = w->size - w->pos; + if(a == 0) { + if(writer_flush(w)) { + return 1; + } + } + + size_t cplen = len > a ? a : len; // number of bytes we can copy + memcpy(w->buffer+w->pos, s, cplen); + w->pos += cplen; + + if(cplen < len) { + // not all bytes copied -> call writer_put again + // the number of available bytes is 0 then, therefore flush is called + return writer_put(w, s + cplen, len - cplen); + } else { + return 0; + } +} + +int writer_puts(Writer *w, sstr_t s) { + return writer_put(w, s.ptr, s.length); +} + +int writer_putc(Writer *w, char c) { + if(w->pos == w->size) { + if(writer_flush(w)) { + return 1; + } + } + w->buffer[w->pos++] = c; + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/writer.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WRITER_H +#define WRITER_H + +#include "../public/nsapi.h" +#include <ucx/string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Writer { + SYS_NETFD fd; + char *buffer; + size_t size; + size_t pos; + int error; +} Writer; + +void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len); + +int writer_flush(Writer *w); + +int writer_put(Writer *w, const char *s, size_t len); + +int writer_puts(Writer *w, sstr_t s); + +int writer_putc(Writer *w, char c); + +#ifdef __cplusplus +} +#endif + +#endif /* WRITER_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,626 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "../daemon/session.h" +#include "../daemon/protocol.h" +#include "../util/platform.h" + +#include <ucx/string.h> + +#include "multistatus.h" + +#include "operation.h" +#include "xml.h" + +#define MULTISTATUS_BUFFER_LENGTH 2048 + +Multistatus* multistatus_response(Session *sn, Request *rq) { + Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus)); + if(!ms) { + return NULL; + } + ZERO(ms, sizeof(Multistatus)); + ms->response.addresource = multistatus_addresource; + ms->sn = sn; + ms->rq = rq; + ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8); + ms->proppatch = FALSE; + if(!ms->namespaces) { + return NULL; + } + if(ucx_map_cstr_put(ms->namespaces, "D", webdav_dav_namespace())) { + return NULL; + } + return ms; +} + +static int send_xml_root(Multistatus *ms, Writer *out) { + writer_puts(out, S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<D:multistatus")); + + // write the namespaces definitions + // key is the namespace prefix + // the map always contains the "DAV:" namespace with the prefix "D" + UcxMapIterator i = ucx_map_iterator(ms->namespaces); + WSNamespace *ns; + UCX_MAP_FOREACH(key, ns, i) { + writer_puts(out, S(" xmlns:")); + writer_put(out, key.data, key.len); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)ns->href)); + writer_puts(out, S("\"")); + } + + writer_puts(out, S(">\n")); + + return out->error; +} + +static void send_nsdef(WSNamespace *ns, Writer *out) { + writer_puts(out, S(" xmlns:")); + writer_puts(out, sstr((char*)ns->prefix)); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)ns->href)); + writer_putc(out, '\"'); +} + +static int send_property( + Multistatus *ms, + WebdavProperty *property, + WebdavNSList *nsdef, + WSBool writeContent, + Writer *out) +{ + // write: "<prefix:name" + writer_putc(out, '<'); + writer_puts(out, sstr((char*)property->namespace->prefix)); + writer_putc(out, ':'); + writer_puts(out, sstr((char*)property->name)); + + // check if the namespace is already defined + WSBool need_nsdef = TRUE; + WSNamespace *ns = ucx_map_cstr_get( + ms->namespaces, + (char*)property->namespace->prefix); + if(ns && !strcmp( + (const char*)ns->href, + (const char*)property->namespace->href)) + { + need_nsdef = FALSE; // prefix and href are the same, no need for nsdef + } + + // send definition for the element's namespace + if(need_nsdef) { + send_nsdef(property->namespace, out); + } + + // send additional namespace definitions required for the value + WebdavNSList *def = nsdef; + while(def) { + send_nsdef(def->namespace, out); + def = def->next; + } + + // send xml lang attribute + if(property->lang) { + writer_puts(out, S(" xml:lang=\"")); + writer_puts(out, sstr((char*)property->lang)); + writer_putc(out, '\"'); + } + + // end property tag and write content + if(writeContent) { + writer_putc(out, '>'); + + // content + switch(property->vtype) { + case WS_VALUE_NO_TYPE: break; + case WS_VALUE_XML_NODE: { + wsxml_write_nodes_without_nsdef( + ms->sn->pool, + out, + property->value.node); + break; + } + case WS_VALUE_XML_DATA: { + // only write data, data->namespaces is already handled + writer_put( + out, + property->value.data->data, + property->value.data->length); + break; + } + case WS_VALUE_TEXT: { + // asume the text is already escaped + writer_put( + out, + property->value.text.str, + property->value.text.length); + break; + } + } + + // end tag + writer_puts(out, S("</")); + writer_puts(out, sstr((char*)property->namespace->prefix)); + writer_putc(out, ':'); + writer_puts(out, sstr((char*)property->name)); + writer_putc(out, '>'); + } else { + writer_puts(out, S("/>")); + } + + return out->error; +} + +static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) { + writer_puts(out, S(" <D:response>\n" + " <D:href>")); + writer_puts(out, sstr(rp->resource.href)); + writer_puts(out, S("</D:href>\n")); + + WSBool writeContent = ms->proppatch ? FALSE : TRUE; + + if(rp->plist_begin) { + writer_puts(out, S(" <D:propstat>\n" + " <D:prop>\n")); + // send properties + PropertyOkList *p = rp->plist_begin; + while(p) { + writer_puts(out, S(" ")); + if(send_property(ms, p->property, p->nsdef, writeContent, out)) { + return out->error; + } + writer_puts(out, S("\n")); + p = p->next; + } + + writer_puts(out, S(" </D:prop>\n" + " <D:status>HTTP/1.1 200 OK</D:status>\n" + " </D:propstat>\n")); + } + + // send error properties + PropertyErrorList *error = rp->errors; + while(error) { + writer_puts(out, S(" <D:propstat>\n" + " <D:prop>\n")); + + WebdavPList *errprop = error->begin; + while(errprop) { + writer_puts(out, S(" ")); + if(send_property(ms, errprop->property, NULL, FALSE, out)) { + return out->error; + } + writer_putc(out, '\n'); + errprop = errprop->next; + } + + char statuscode[8]; + int sclen = snprintf(statuscode, 8, "%d ", error->status); + if(sclen > 4) { + statuscode[0] = '5'; + statuscode[1] = '0'; + statuscode[2] = '0'; + statuscode[3] = ' '; + sclen = 4; + } + writer_puts(out, S(" </D:prop>\n" + " <D:status>HTTP/1.1 ")); + writer_put(out, statuscode, sclen); + const char *status_msg = protocol_status_message(error->status); + if(status_msg) { + writer_put(out, status_msg, strlen(status_msg)); + } else { + writer_puts(out, S("Server Error")); + } + writer_puts(out, S("</D:status>\n" + " </D:propstat>\n")); + + + error = error->next; + } + + // end response tag + writer_puts(out, S(" </D:response>\n")); + + return out->error; +} + +int multistatus_send(Multistatus *ms, SYS_NETFD net) { + // start http response + protocol_status(ms->sn, ms->rq, 207, NULL); + protocol_start_response(ms->sn, ms->rq); + + char buffer[MULTISTATUS_BUFFER_LENGTH]; + // create a writer, that flushes the buffer when it is filled + Writer writer; + Writer *out = &writer; + writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH); + + // send the xml root element with namespace defs + if(send_xml_root(ms, out)) { + return 1; + } + + // send response tags + MSResponse *response = ms->first; + while(response) { + if(send_response_tag(ms, response, out)) { + return 1; + } + response = response->next; + } + + // end multistatus + writer_puts(out, S("</D:multistatus>\n")); + + writer_flush(out); + + return 0; +} + +WebdavResource * multistatus_addresource( + WebdavResponse *response, + const char *path) +{ + Multistatus *ms = (Multistatus*)response; + MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse)); + if(!res) { + return NULL; + } + ZERO(res, sizeof(MSResponse)); + + // set href + res->resource.href = pool_strdup(ms->sn->pool, path); + if(!res->resource.href) { + return NULL; + } + + res->resource.err = 0; + + // add resource funcs + res->resource.addproperty = msresponse_addproperty; + res->resource.close = msresponse_close; + + res->properties = ucx_map_new_a(session_get_allocator(ms->sn), 32); + if(!res->properties) { + return NULL; + } + + res->multistatus = ms; + res->errors = NULL; + res->resource.isclosed = 0; + res->closing = 0; + + // add new resource to the resource list + if(ms->current) { + // before adding a new resource, the current resource must be closed + if(!ms->current->resource.isclosed) { + msresponse_close((WebdavResource*)ms->current); + } + ms->current->next = res; + } else { + ms->first = res; + } + ms->current = res; + + return (WebdavResource*)res; +} + +static int oklist_add( + pool_handle_t *pool, + PropertyOkList **begin, + PropertyOkList **end, + WebdavProperty *property, + WebdavNSList *nsdef) +{ + PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList)); + if(!newelm) { + return 1; + } + newelm->property = property; + newelm->nsdef = nsdef; + newelm->next = NULL; + if(*end) { + (*end)->next = newelm; + } else { + *begin = newelm; + } + *end = newelm; + return 0; +} + +int msresponse_addproperty( + WebdavResource *res, + WebdavProperty *property, + int status) +{ + MSResponse *response = (MSResponse*)res; + Session *sn = response->multistatus->sn; + if(response->resource.isclosed) { + log_ereport( + LOG_WARN, + "%s", + "webdav: cannot add property to closed response tag"); + return 0; + } + + // some WebdavProperty checks to make sure nothing explodes + if(!property->namespace || !property->namespace->href) { + // error: namespace is required + log_ereport( + LOG_FAILURE, + "%s", + "webdav: property '%s' has no namespace", + property->name); + return 1; + } + + // check if the property was already added to the resource + UcxAllocator *a = session_get_allocator(sn); + sstr_t key = sstrcat_a( + a, + 3, + sstr((char*)property->namespace->href), + S("\0"), + sstr((char*)property->name)); + if(ucx_map_sstr_get(response->properties, key)) { + a->free(a->pool, key.ptr); + return 0; + } + if(ucx_map_sstr_put(response->properties, key, property)) { + return 1; // OOM + } + a->free(a->pool, key.ptr); + + // list of namespace definitions for this property + WebdavNSList *nsdef_begin = NULL; + WebdavNSList *nsdef_end = NULL; + + // add namespace of this property to the namespace map + // the namespace map will be used for global namespace definitions + if(property->namespace->prefix) { + WSNamespace *ns = ucx_map_cstr_get( + response->multistatus->namespaces, + (const char*)property->namespace->prefix); + if(!ns) { + // prefix is not in use -> we can add the namespace to the ns map + int err = ucx_map_cstr_put( + response->multistatus->namespaces, + (const char*)property->namespace->prefix, + property->namespace); + if(err) { + return 1; // OOM + } + } else if( + strcmp((const char*)property->namespace->href, + (const char*)ns->href)) + { + // global namespace != local namespace + // therefore we need a namespace definition in this element + + // ns-prefix != property-prefix -> add ns to nsdef + if(webdav_nslist_add( + sn->pool, + &nsdef_begin, + &nsdef_end, + property->namespace)) + { + return 1; // OOM + } + } + } + + if(response->multistatus->proppatch && response->errors) { + // in a proppatch request all operations must succeed + // if we have an error, the property update status code must be + // 424 Failed Dependency + status = 424; + } + + // error properties will be added to a separate list + if(status != 200) { + return msresponse_addproperror(response, property, status); + } + + // add all namespaces used by this property to the nsdef list + WebdavNSList *nslist = NULL; + if(property->vtype == WS_VALUE_XML_NODE) { + // iterate over xml tree and collect all namespaces + int err = 0; + nslist = wsxml_get_required_namespaces( + response->multistatus->sn->pool, + property->value.node, + &err); + if(err) { + return 1; // OOM + } + } else if(property->vtype == WS_VALUE_XML_DATA) { + // xml data contains a list of all used namespaces + nslist = property->value.data->namespaces; + } // other value types don't contain xml namespaces + + while(nslist) { + // only add the namespace to the definitions list, if it isn't a + // property namespace, because the prop ns is already added + // to the element's def list or global definitions list + if(strcmp( + (const char*)nslist->namespace->prefix, + (const char*)property->namespace->prefix)) + { + // ns-prefix != property-prefix -> add ns to nsdef + if(webdav_nslist_add( + sn->pool, + &nsdef_begin, + &nsdef_end, + nslist->namespace)) + { + return 1; // OOM + } + } + nslist = nslist->next; + } + + // add property to the list + if(oklist_add( + sn->pool, + &response->plist_begin, + &response->plist_end, + property, + nsdef_begin)) + { + return 1; + } + return 0; +} + +int msresponse_addproperror( + MSResponse *response, + WebdavProperty *property, + int statuscode) +{ + pool_handle_t *pool = response->multistatus->sn->pool; + UcxAllocator *a = session_get_allocator(response->multistatus->sn); + + response->resource.err++; + + // MSResponse contains a list of properties for each status code + // at first find the list for this status code + PropertyErrorList *errlist = NULL; + PropertyErrorList *list = response->errors; + PropertyErrorList *last = NULL; + while(list) { + if(list->status == statuscode) { + errlist = list; + break; + } + last = list; + list = list->next; + } + + if(!errlist) { + // no list available for this statuscode + PropertyErrorList *newelm = pool_malloc(pool, + sizeof(PropertyErrorList)); + if(!newelm) { + return 1; + } + newelm->begin = NULL; + newelm->end = NULL; + newelm->next = NULL; + newelm->status = statuscode; + + if(last) { + last->next = newelm; + } else { + response->errors = newelm; + } + errlist = newelm; + } + + // we have the list -> add the new element + if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) { + return 1; + } + return 0; +} + +int msresponse_close(WebdavResource *res) { + MSResponse *response = (MSResponse*)res; + if(response->closing) { + return 0; // close already in progress + } + response->closing = TRUE; + Multistatus *ms = response->multistatus; + + int ret = REQ_PROCEED; + + // PROPFIND: + // response_close will execute propfind_do of all remaining backends + // after that we will have all available properties + WebdavOperation *op = ms->response.op; + if(op->response_close(op, res)) { + ret = REQ_ABORTED; + } + + // add missing properties with status code 404 + UcxAllocator *a = session_get_allocator(ms->sn); + WebdavPList *pl = ms->response.op->reqprops; + while(pl) { + sstr_t key = sstrcat_a( + a, + 3, + sstr((char*)pl->property->namespace->href), + S("\0"), + sstr((char*)pl->property->name)); + if(!ucx_map_sstr_get(response->properties, key)) { + // property was not added to this response + if(ms->proppatch) { + if(msresponse_addproperror(response, pl->property, 424)) { + ret = REQ_ABORTED; + break; + } + } else { + if(msresponse_addproperror(response, pl->property, 404)) { + ret = REQ_ABORTED; + break; + } + } + } + + pl = pl->next; + } + + if(ms->proppatch && response->errors) { + // a proppatch response must succeed entirely + // if we have a single error prop, move all props with status 200 + // to the error list + PropertyOkList *elm = response->plist_begin; + PropertyOkList *nextelm; + while(elm) { + if(msresponse_addproperror(response, elm->property, 424)) { + return 1; + } + nextelm = elm->next; + pool_free(response->multistatus->sn->pool, elm); + elm = nextelm; + } + response->plist_begin = NULL; + response->plist_end = NULL; + } + + // we don't need the properties anymore + ucx_map_free(response->properties); + + response->resource.isclosed = TRUE; + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,157 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef MULTISTATUS_H +#define MULTISTATUS_H + +#include "../public/webdav.h" + +#include <ucx/map.h> +#include <ucx/buffer.h> +#include <libxml/tree.h> +#include "../util/writer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Multistatus Multistatus; +typedef struct MSResponse MSResponse; + +typedef struct PropertyOkList PropertyOkList; +typedef struct PropertyErrorList PropertyErrorList; + +/* + * implements the WebdavResponse interface + */ +struct Multistatus { + WebdavResponse response; + Session *sn; + Request *rq; + MSResponse *first; + MSResponse *current; + + /* + * Map of document namespace definitions + * + * key: (char*) namespace prefix + * value: WSNamespace* + */ + UcxMap *namespaces; + + /* + * Is this a proppatch request? + * + * In a proppatch response, when the first property with an error occurs, + * all already added properties will be set to 424 Failed Dependency. + */ + WSBool proppatch; +}; + +/* + * implements the WebdavResource interface + */ +struct MSResponse { + WebdavResource resource; + Multistatus *multistatus; + + /* + * Contains all properties that were added to the response + * key: <href> null-byte <name> + * value: WebdavProperty* + */ + UcxMap *properties; + + /* + * All properties with status != 200 + */ + PropertyErrorList *errors; + + /* + * All properties with status == 200 + */ + PropertyOkList *plist_begin; + PropertyOkList *plist_end; + + MSResponse *next; + WSBool closing; +}; + +struct PropertyOkList { + WebdavProperty *property; + WebdavNSList *nsdef; + PropertyOkList *next; +}; + +struct PropertyErrorList { + /* + * next list for different status code + */ + PropertyErrorList *next; + + /* + * property list for all properties with this status code + */ + WebdavPList *begin; + + /* + * tail of the property list + */ + WebdavPList *end; + + /* + * property response status code + */ + int status; +}; + +Multistatus* multistatus_response(Session *sn, Request *rq); + +int multistatus_send(Multistatus *ms, SYS_NETFD out); + +WebdavResource * multistatus_addresource( + WebdavResponse *response, + const char *path); + +int msresponse_addproperty( + WebdavResource *res, + WebdavProperty *property, + int status); + +int msresponse_addproperror( + MSResponse *response, + WebdavProperty *property, + int statuscode); + +int msresponse_close(WebdavResource *res); + +#ifdef __cplusplus +} +#endif + +#endif /* MULTISTATUS_H */ +
--- a/src/server/webdav/objs.mk Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/webdav/objs.mk Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. # -# Copyright 2013 Olaf Wintermann. All rights reserved. +# Copyright 2019 Olaf Wintermann. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -31,6 +31,12 @@ DAV_OBJPRE = $(OBJ_DIR)$(DAV_SRC_DIR) DAVOBJ = webdav.o +DAVOBJ += xml.o +DAVOBJ += requestparser.o +DAVOBJ += operation.o +DAVOBJ += multistatus.o +DAVOBJ += search.o +DAVOBJ += versioning.o DAVOBJS = $(DAVOBJ:%=$(DAV_OBJPRE)%) DAVSOURCE = $(DAVOBJ:%.o=webdav/%.c)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/operation.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,752 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <ucx/list.h> + +#include "../daemon/session.h" +#include "../util/pblock.h" + +#include "operation.h" + +#define WEBDAV_PATH_MAX 8192 + + +size_t webdav_num_backends(WebdavBackend *dav) { + size_t count = 0; + while(dav) { + count++; + dav = dav->next; + } + return count; +} + +/**************************************************************************** + * + * PROPFIND OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_propfind_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavPList *reqprops, + UcxList *requests, + WebdavResponse *response) +{ + WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); + ZERO(op, sizeof(WebdavOperation)); + op->dav = dav; + op->sn = sn; + op->rq = rq; + op->reqprops = reqprops; + op->requests = requests; + op->response = response; + op->response_close = webdav_op_propfiond_close_resource; + response->op = op; + + return op; +} + +int webdav_op_propfind_begin( + WebdavOperation *op, + const char *href, + VFS_DIR parent, + struct stat *s) +{ + // create WebdavResource object for requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + // store data that we need when the resource will be closed + op->stat = s; + op->parent = parent; + + // get first propfind object + WebdavPropfindRequest *propfind = op->requests->data; + + // execute propfind_do of the first backend for the first resource + int ret = REQ_PROCEED; + if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) { + ret = REQ_ABORTED; + } else { + // propfind_do successful, close resource if needed + // closing the resource will execute propfind_do of all remaining + // backends + if(!resource->isclosed) { + ret = resource->close(resource); + } + } + + return ret; +} + +typedef struct PathSearchElm { + char *href; + char *path; + size_t hreflen; + size_t pathlen; +} PathSearchElm; + +/* + * concats base + / + elm + * if baseinit is true, only elm is copied + */ +static int path_buf_concat( + pool_handle_t *pool, + char **buf, + size_t * restrict len, + WSBool * restrict baseinit, + const char *base, + size_t baselen, + const char *elm, + size_t elmlen) +{ + if(base[baselen-1] == '/') { + baselen--; + } + + size_t newlen = baselen + elmlen + 1; + if(newlen > WEBDAV_PATH_MAX) { + log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded"); + return 1; + } + + // check if new path + terminator fits in the buffer + if(newlen + 1 > *len) { + *len = newlen + 128; + char *newbuf = pool_realloc(pool, *buf, newlen); + if(newbuf) { + log_ereport(LOG_FAILURE, "webdav: path memory allocation failed"); + return 1; + } + *baseinit = FALSE; + + *buf = newbuf; + } + + // if baseinit is true, the parent is already in the buffer + // and we don't need to memcpy it again + if(!(*baseinit)) { + memcpy(*buf, base, baselen); + (*buf)[baselen] = '/'; + *baseinit = TRUE; + } + // copy child and terminate string + memcpy((*buf) + baselen + 1, elm, elmlen); + (*buf)[newlen] = '\0'; + + return 0; +} + +static int propfind_child_cb( + VFSContext *vfs, + const char *href, + const char *path, + VFSDir *parent, + struct stat *s, + void *op) +{ + return webdav_op_propfind_begin(op, href, parent, s); +} + +int webdav_op_propfind_children( + WebdavOperation *op, + VFSContext *vfs, + const char *href, + const char *path) +{ + WebdavPropfindRequest *request = op->requests->data; + return webdav_op_iterate_children( + vfs, request->depth, href, path, propfind_child_cb, op); +} + +int webdav_op_propfiond_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + // start with second backend and request, because + // the first one was already called by webdav_op_propfind_begin + WebdavBackend *dav = op->dav->next; + UcxList *request = op->requests->next; + + // call propfind_do of all remaining backends + int ret = REQ_PROCEED; + while(dav && request) { + if(dav->propfind_do( + request->data, + op->response, + op->parent, + resource, + op->stat)) + { + ret = REQ_ABORTED; + } + + dav = dav->next; + request = request->next; + } + return ret; +} + +/* + * Executes propfind_finish for each Backend + */ +int webdav_op_propfind_finish(WebdavOperation *op) { + WebdavBackend *dav = op->dav; + UcxList *requests = op->requests; + + int ret = REQ_PROCEED; + while(dav && requests) { + if(dav->propfind_finish(requests->data)) { + ret = REQ_ABORTED; + } + + dav = dav->next; + requests = requests->next; + } + return ret; +} + +/**************************************************************************** + * + * PROPPATCH OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response) +{ + WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); + ZERO(op, sizeof(WebdavOperation)); + op->dav = dav; + op->sn = sn; + op->rq = rq; + op->reqprops = NULL; + op->response = response; + op->proppatch = proppatch; + op->response_close = webdav_op_proppatch_close_resource; + response->op = op; + + return op; +} + + + +int webdav_op_proppatch( + WebdavOperation *op, + const char *href, + const char *path) +{ + WebdavProppatchRequest *orig_request = op->proppatch; + UcxAllocator *a = session_get_allocator(op->sn); + + // create WebdavResource object for the requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + VFSContext *ctx = NULL; + VFSFile *file = NULL; + + // requests for each backends + WebdavProppatchRequest **requests = pool_calloc( + op->sn->pool, + webdav_num_backends(op->dav), + sizeof(WebdavProppatchRequest*)); + if(requests == NULL) { + return REQ_ABORTED; + } + + WebdavPList *prev_set = orig_request->set; + WebdavPList *prev_remove = orig_request->remove; + size_t set_count = orig_request->setcount; + size_t remove_count = orig_request->removecount; + + int ret = REQ_PROCEED; + + // iterate backends and execute proppatch_do + WebdavBackend *dav = op->dav; + size_t numrequests = 0; + while(dav) { + WebdavPList *set = webdav_plist_clone_s( + op->sn->pool, + prev_set, + &set_count); + WebdavPList *remove = webdav_plist_clone_s( + op->sn->pool, + prev_remove, + &remove_count); + if((prev_set && !set) || (prev_remove && !remove)) { + // clone failed, OOM + ret = REQ_ABORTED; + break; + } + + // create new WebdavProppatchRequest object for this backend + WebdavProppatchRequest *req = pool_malloc( + op->sn->pool, + sizeof(WebdavProppatchRequest)); + memcpy(req, orig_request, sizeof(WebdavProppatchRequest)); + req->set = set; + req->setcount = set_count; + req->remove = remove; + req->removecount = remove_count; + req->userdata = NULL; + + // check if we need to open the file because the backend want's it + if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS) + == WS_WEBDAV_PROPPATCH_USE_VFS) + { + ctx = vfs_request_context(op->sn, op->rq); + if(!ctx) { + ret = REQ_ABORTED; + break; + } + + file = vfs_open(ctx, path, O_RDONLY); + if(!file) { + protocol_status( + op->sn, + op->rq, + util_errno2status(ctx->vfs_errno), + NULL); + ret = REQ_ABORTED; + } + } + + // execute proppatch_do + if(dav->proppatch_do(req, resource, file, &set, &remove)) { + // return later, because we need do execute proppatch_finish + // for all successfully called backends + ret = REQ_ABORTED; + break; + } + + // proppatch_do should remove all handled props from set and remove + // in the next iteration, the backend must use these reduced lists + prev_set = set; + prev_remove = remove; + + requests[numrequests++] = req; + + // continue with next backend + dav = dav->next; + } + + WSBool commit = FALSE; + if(ret == REQ_PROCEED && resource->err == 0) { + // no errors, no properties with errors -> save the changes + commit = TRUE; + } + + // call proppatch_finish for each successfully called proppatch_do + dav = op->dav; + int i = 0; + while(dav && i < numrequests) { + if(dav->proppatch_finish(requests[i], resource, file, commit)) { + ret = REQ_ABORTED; + } + i++; + dav = dav->next; + } + + if(file) { + vfs_close(file); + } + + if(resource->close(resource)) { + ret = REQ_ABORTED; + } + + return ret; +} + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + return 0; // NOP +} + + +/**************************************************************************** + * + * VFS OPERATION + * + ****************************************************************************/ + +WebdavVFSOperation* webdav_vfs_op( + Session *sn, + Request *rq, + WebdavBackend *dav, + WSBool precondition) +{ + WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation)); + if(!op) { + return NULL; + } + ZERO(op, sizeof(WebdavVFSOperation)); + + op->sn = sn; + op->rq = rq; + op->dav = dav; + op->stat = NULL; + op->stat_errno = 0; + + // create VFS context + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + pool_free(sn->pool, op); + return NULL; + } + op->vfs = vfs; + + char *path = pblock_findkeyval(pb_key_path, rq->vars); + op->path = path; + + return op; +} + +WebdavVFSOperation webdav_vfs_sub_op( + WebdavVFSOperation *op, + char *path, + struct stat *s) +{ + WebdavVFSOperation sub; + sub.dav = op->dav; + sub.path = path; + sub.sn = op->sn; + sub.vfs = op->vfs; + sub.path = path; + sub.stat = s; + sub.stat_errno = 0; + return sub; +} + +int webdav_op_iterate_children( + VFSContext *vfs, + int depth, + const char *href, + const char *path, + vfs_op_child_func func, + void *userdata) +{ + UcxAllocator *a = session_get_allocator(vfs->sn); + pool_handle_t *pool = vfs->sn->pool; + + PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm)); + start_elm->href = pool_strdup(pool, href ? href : ""); + start_elm->path = pool_strdup(pool, path ? path : ""); + start_elm->hreflen = href ? strlen(href) : 0; + start_elm->pathlen = path ? strlen(path) : 0; + + UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm); + UcxList *stack_end = stack; + if(!stack) { + return 1; + } + + // reusable buffer for full child path and href + char *newpath = pool_malloc(pool, 256); + size_t newpathlen = 256; + + char *newhref = pool_malloc(pool, 256); + size_t newhreflen = 256; + + int err = 0; + while(stack && !err) { + PathSearchElm *cur_elm = stack->data; + + // when newpath is initialized with the parent path + // set path_buf_init to TRUE + WSBool href_buf_init = FALSE; + WSBool path_buf_init = FALSE; + + VFS_DIR dir = vfs_opendir(vfs, cur_elm->path); + if(!dir) { + log_ereport( + LOG_FAILURE, + "webdav: propfind: cannot open directory %d", + vfs->vfs_errno); + err = 1; + break; + } + + VFS_ENTRY f; + while(vfs_readdir_stat(dir, &f)) { + if(f.stat_errno != 0) { + continue; + } + + size_t child_len = strlen(f.name); + + // create new path and href for the child + if(path_buf_concat( + pool, + &newhref, + &newhreflen, + &href_buf_init, + cur_elm->href, + cur_elm->hreflen, + f.name, + child_len)) + { + err = 1; + break; + } + if(path_buf_concat( + pool, + &newpath, + &newpathlen, + &path_buf_init, + cur_elm->path, + cur_elm->pathlen, + f.name, + child_len)) + { + err = 1; + break; + } + size_t childhreflen = cur_elm->hreflen + 1 + child_len; + size_t childpathlen = cur_elm->pathlen + 1 + child_len; + + // execute callback func for this file + if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) { + err = 1; + break; + } + + // depth of -1 means infinity + if(depth == -1 && S_ISDIR(f.stat.st_mode)) { + char *hrefcp = pool_malloc(pool, childhreflen + 1); + memcpy(hrefcp, newhref, childhreflen + 1); + hrefcp[childhreflen] = '\0'; + + char *pathcp = pool_malloc(pool, childpathlen + 1); + memcpy(pathcp, newpath, childpathlen + 1); + pathcp[childpathlen] = '\0'; + + PathSearchElm *new_elm = pool_malloc(pool, + sizeof(PathSearchElm)); + new_elm->href = hrefcp; + new_elm->path = pathcp; + new_elm->hreflen = childhreflen; + new_elm->pathlen = childpathlen; + + // add the new_elm to the stack + // stack_end is always not NULL here, because we remove + // the first stack element at the end of the loop + UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm); + if(!newlistelm) { + err = 1; + break; + } + stack_end = newlistelm; + } + } + + vfs_closedir(dir); + + pool_free(pool, cur_elm->path); + pool_free(pool, cur_elm->href); + pool_free(pool, cur_elm); + + stack = ucx_list_remove_a(a, stack, stack); + } + + // in case of an error, we have to free all remaining stack elements + UCX_FOREACH(elm, stack) { + char *data = elm->data; + if(data != path) { + pool_free(pool, data); + } + } + + return err; +} + + +int webdav_vfs_stat(WebdavVFSOperation *op) { + if(op->stat) { + return 0; + } else if(op->stat_errno != 0) { + // previous stat failed + return 1; + } + + // stat file + struct stat sbuf; + int ret = vfs_stat(op->vfs, op->path, &sbuf); + if(!ret) { + // save result in op->stat and in s + op->stat = pool_malloc(op->sn->pool, sizeof(struct stat)); + if(op->stat) { + memcpy(op->stat, &sbuf, sizeof(struct stat)); + } else { + ret = 1; + op->stat_errno = ENOMEM; + } + } else { + op->stat_errno = errno; + } + + return ret; +} + +int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) { + WSBool exec_vfs = TRUE; + + // requests for each backends + WebdavVFSRequest **requests = pool_calloc( + op->sn->pool, + webdav_num_backends(op->dav), + sizeof(WebdavVFSRequest*)); + if(requests == NULL) { + return REQ_ABORTED; + } + + int ret = REQ_PROCEED; + + // call opt_* func for each backend + WebdavBackend *dav = op->dav; + int called_backends = 0; + while(dav) { + WebdavVFSRequest *request = NULL; + + // get vfs operation functions + vfs_op_func op_func = NULL; + vfs_op_finish_func op_finish_func = NULL; + + if(type == WEBDAV_VFS_MKDIR) { + op_func = dav->opt_mkcol; + op_finish_func = dav->opt_mkcol_finish; + } else if(type == WEBDAV_VFS_DELETE) { + op_func = dav->opt_delete; + op_finish_func = dav->opt_delete_finish; + } + + if(op_func || op_finish_func) { + // we need a request object + request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest)); + if(!request) { + exec_vfs = FALSE; + ret = REQ_ABORTED; + break; + } + request->sn = op->sn; + request->rq = op->rq; + request->path = op->path; + request->userdata = NULL; + + requests[called_backends] = request; + } + + // exec backend func for this operation + // this will set 'done' to TRUE, if no further vfs call is required + WSBool done = FALSE; + called_backends++; + if(op_func) { + if(op_func(request, &done)) { + exec_vfs = FALSE; + ret = REQ_ABORTED; + break; + } + } + if(done) { + exec_vfs = FALSE; + } + + dav = dav->next; + } + + // if needed, call vfs func for this operation + if(exec_vfs) { + int r = 0; + if(type == WEBDAV_VFS_MKDIR) { + r = vfs_mkdir(op->vfs, op->path); + } else if(type == WEBDAV_VFS_DELETE) { + r = webdav_vfs_unlink(op); + } + + if(r) { + ret = REQ_ABORTED; + } + } + + WSBool success = ret == REQ_PROCEED ? TRUE : FALSE; + + // finish mkcol (cleanup) by calling opt_*_finish for each backend + dav = op->dav; + int i = 0; + while(dav && i < called_backends) { + // get vfs operation functions + vfs_op_finish_func op_finish_func = NULL; + + if(type == WEBDAV_VFS_MKDIR) { + op_finish_func = dav->opt_mkcol_finish; + } else if(type == WEBDAV_VFS_DELETE) { + op_finish_func = dav->opt_delete_finish; + } + + if(op_finish_func) { + if(op_finish_func(requests[i], success)) { + ret = REQ_ABORTED; // don't exit loop + } + } + + dav = dav->next; + i++; + } + + return ret; +} + +int webdav_vfs_unlink(WebdavVFSOperation *op) { + // stat the file first, to check if the file is a directory + if(webdav_vfs_stat(op)) { + return 1; // error + } else { + if(!S_ISDIR(op->stat->st_mode)) { + return vfs_unlink(op->vfs, op->path); + } else { + return vfs_rmdir(op->vfs, op->path); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/operation.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,199 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OPERATION_H +#define OPERATION_H + +#include "../public/webdav.h" +#include <ucx/list.h> +#include <ucx/map.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int(*response_close_func)(WebdavOperation *, WebdavResource *); + +typedef struct WebdavVFSOperation WebdavVFSOperation; + +typedef struct WebdavCopy WebdavCopy; +typedef struct CopyResource CopyResource; + +struct WebdavOperation { + WebdavBackend *dav; + Request *rq; + Session *sn; + + WebdavProppatchRequest *proppatch; /* proppatch request or NULL */ + WebdavPList *reqprops; /* requested properties */ + UcxList *requests; /* backend specific request objects */ + + WebdavResponse *response; + + response_close_func response_close; + + VFS_DIR parent; /* current directory */ + struct stat *stat; /* current stat object */ +}; + +struct WebdavVFSOperation { + WebdavBackend *dav; + Request *rq; + Session *sn; + + VFSContext *vfs; + + char *path; + struct stat *stat; + int stat_errno; +}; + +struct WebdavCopy { + WebdavResponse response; + Session *sn; + Request *rq; + CopyResource *current; + + char *src_href; + char *src_path; + char *dst_href; + char *dst_path; +}; + +struct CopyResource { + WebdavResource resource; + UcxMap *properties; +}; + +enum WebdavVFSOpType { + WEBDAV_VFS_MKDIR = 0, + WEBDAV_VFS_DELETE +}; + +typedef enum WebdavVFSOpType WebdavVFSOpType; + +typedef int(*vfs_op_func)(WebdavVFSRequest *, WSBool *); +typedef int(*vfs_op_finish_func)(WebdavVFSRequest *, WSBool); + +typedef int(*vfs_op_child_func)( + VFSContext *, + const char *, /* href */ + const char *, /* path */ + VFSDir *, /* parent dir */ + struct stat *, /* child stat */ + void *); /* user data */ + +/* + * counts the number of backends + */ +size_t webdav_num_backends(WebdavBackend *dav); + +WebdavOperation* webdav_create_propfind_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavPList *reqprops, + UcxList *requests, + WebdavResponse *response); + +int webdav_op_propfind_begin( + WebdavOperation *op, + const char *href, + VFS_DIR parent, + struct stat *s); + +int webdav_op_propfind_children( + WebdavOperation *op, + VFSContext *vfs, + const char *href, + const char *path); + +int webdav_op_propfiond_close_resource( + WebdavOperation *op, + WebdavResource *resource); + +int webdav_op_propfind_finish(WebdavOperation *op); + +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response); + +int webdav_op_proppatch( + WebdavOperation *op, + const char *href, + const char *path); + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource); + + +WebdavVFSOperation* webdav_vfs_op( + Session *sn, + Request *rq, + WebdavBackend *dav, + WSBool precondition); + +WebdavVFSOperation webdav_vfs_sub_op( + WebdavVFSOperation *op, + char *path, + struct stat *s); + +int webdav_op_iterate_children( + VFSContext *vfs, + int depth, + const char *href, + const char *path, + vfs_op_child_func func, + void *userdata); + +int webdav_vfs_stat(WebdavVFSOperation *op); + +int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type); + +int webdav_vfs_unlink(WebdavVFSOperation *op); + + +WebdavCopy* webdav_copy_create( + Session *sn, + Request *rq, + VFSContext *vfs, + char *from_href, + char *from_path, + char *to_href, + char *to_path); + +#ifdef __cplusplus +} +#endif + +#endif /* OPERATION_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/requestparser.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,504 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <ucx/string.h> +#include <ucx/utils.h> +#include <ucx/map.h> + +#include "requestparser.h" + +#define xstreq(a, b) !strcmp((const char*)a, (const char*)b) + +void proplist_free(pool_handle_t *pool, WebdavPList *list) { + while(list) { + WebdavPList *next = list->next; + pool_free(pool, list); + list = next; + } +} + +WebdavProperty* prop_create( + pool_handle_t *pool, + WSNamespace *ns, + const char *name) +{ + WebdavProperty *prop = pool_malloc(pool, sizeof(WebdavProperty)); + memset(prop, 0, sizeof(WebdavProperty)); + prop->lang = NULL; + prop->name = (char*)name; + prop->namespace = ns; + return prop; +} + +static UcxKey propkey(const char *ns, const char *name) { + UcxKey key; + sstr_t data = ucx_sprintf("%s\n%s", name, ns); + key.data = data.ptr; + key.len = data.length; + key.hash = ucx_hash(data.ptr, data.length); + return key; +} + +static int parse_prop( + Session *sn, + xmlNode *node, + UcxMap *propmap, + WebdavPList **plist_begin, + WebdavPList **plist_end, + size_t *propcount, + int proppatch, + int *error) +{ + xmlNode *pnode = node->children; + for(;pnode;pnode=pnode->next) { + if(pnode->type != XML_ELEMENT_NODE) { + continue; + } + + const char* ns = (const char*)pnode->ns->href; + const char* name = (const char*)pnode->name; + + // check for prop duplicates + UcxKey k = propkey((const char*)ns, (const char*)name); + if(!k.data) { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + return 1; + } + void *c = ucx_map_get(propmap, k); + if(!c) { + if(ucx_map_put(propmap, k, (void*)1)) { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + + // no duplicate + // create property elment and add it to the list + WebdavProperty *prop = prop_create(sn->pool, pnode->ns, name); + if(proppatch) { + prop->value.node = pnode->children; + prop->vtype = WS_VALUE_XML_NODE; + } + if(prop) { + if(webdav_plist_add(sn->pool, plist_begin, plist_end, prop)) { + *error = proppatch ? + PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + (*propcount)++; + } else { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + } else if(proppatch) { + *error = PROPPATCH_PARSER_DUPLICATE; + } + + free(k.data); + if(*error) { + return 1; + } + } + return 0; +} + +WebdavPropfindRequest* propfind_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = PROPFIND_PARSER_INVALID_REQUEST; + return NULL; + } + + // ret vars + *error = 0; + + WSBool allprop = FALSE; + WSBool propname = FALSE; + WebdavPList *plist_begin = NULL; + WebdavPList *plist_end = NULL; + size_t propcount = 0; + int depth = webdav_getdepth(rq); + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is DAV:propfind + if( + !(root->ns + && xstreq(root->ns->href, "DAV:") + && xstreq(root->name, "propfind"))) + { + *error = PROPFIND_PARSER_NO_PROPFIND; + xmlFreeDoc(doc); + return NULL; + } + + UcxMap *propmap = ucx_map_new(32); + if(!propmap) { + *error = PROPFIND_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->ns->href, "DAV:") && !allprop && !propname) { + // a propfind request can contain a prop element + // with specified properties or the allprop or propname + // element + if(xstreq(node->name, "prop")) { + ret = parse_prop( + sn, + node, + propmap, + &plist_begin, + &plist_end, + &propcount, + 0, // proppatch = false + error); + } else if(xstreq(node->name, "allprop")) { + allprop = TRUE; + } else if(xstreq(node->name, "propname")) { + propname = TRUE; + } + } + } + node = node->next; + } + + ucx_map_free(propmap); // no allocated content must be freed + + if(ret) { + // parse_prop failed + // in this case, error is already set + xmlFreeDoc(doc); + return NULL; + } + + if(!allprop && !propname && propcount == 0) { + *error = PROPFIND_PARSER_NO_PROPERTIES; + xmlFreeDoc(doc); + return NULL; + } + + WebdavPropfindRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavPropfindRequest)); + if(!request) { + *error = PROPFIND_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->properties = NULL; + request->propcount = 0; + request->depth = depth; + request->doc = doc; + if(allprop) { + request->allprop = TRUE; + request->propname = FALSE; // we cannot have allprop and propname + } else if(propname) { + request->allprop = FALSE; + request->propname = TRUE; + } else { + request->allprop = FALSE; + request->propname = FALSE; + request->properties = plist_begin; + request->propcount = propcount; + } + + if(!request->properties && plist_begin) { + proplist_free(sn->pool, plist_begin); + } + return request; +} + +WebdavProppatchRequest* proppatch_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + return webdav_parse_set( + sn, rq, buf, buflen, "DAV:", "propertyupdate", TRUE, error); +} + +static xmlNode* find_child(xmlNode *node, const char *name) { + xmlNode *c = node->children; + while(c) { + if(c->ns) { + if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, name)) { + return c; + } + } + c = c->next; + } + return NULL; +} + +WebdavProppatchRequest* webdav_parse_set( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + const char *rootns, + const char *rootname, + WSBool allowremove, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = PROPPATCH_PARSER_INVALID_REQUEST; + return NULL; + } + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is correct + if( + !(root->ns + && xstreq(root->ns->href, rootns) + && xstreq(root->name, rootname))) + { + *error = PROPPATCH_PARSER_NO_PROPERTYUPDATE; + xmlFreeDoc(doc); + return NULL; + } + + // ret vars + *error = 0; + + UcxMap *propmap = ucx_map_new(32); // map for duplicate checking + if(!propmap) { + *error = PROPPATCH_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + + WebdavPList *set_begin = NULL; + WebdavPList *set_end = NULL; + WebdavPList *remove_begin = NULL; + WebdavPList *remove_end = NULL; + size_t set_count = 0; + size_t remove_count = 0; + + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(node->ns && xstreq(node->ns->href, "DAV:")) { + // a propfind request can contain a prop element + // with specified properties or the allprop or propname + // element + if(xstreq(node->name, "set")) { + xmlNode *prop = find_child(node, "prop"); + ret = parse_prop( + sn, + prop, + propmap, + &set_begin, + &set_end, + &set_count, + TRUE, // proppatch = true + error); + } else if(xstreq(node->name, "remove")) { + if(!allowremove) { + *error = PROPPATCH_PARSER_INVALID_REQUEST; + ret = 1; + break; + } + xmlNode *prop = find_child(node, "prop"); + ret = parse_prop( + sn, + prop, + propmap, + &remove_begin, + &remove_end, + &remove_count, + TRUE, // proppatch = true + error); + } + + } + } + node = node->next; + } + + ucx_map_free(propmap); // allocated content must not be freed + + if(set_count + remove_count == 0) { + *error = PROPPATCH_PARSER_NO_PROPERTIES; + ret = 1; + } + + if(ret) { + xmlFreeDoc(doc); + return NULL; + } + + WebdavProppatchRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavProppatchRequest)); + if(!request) { + *error = PROPPATCH_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->doc = doc; + request->set = set_begin; + request->setcount = set_count; + request->remove = remove_begin; + request->removecount = remove_count; + return request; +} + +WebdavLockRequest* lock_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = LOCK_PARSER_INVALID_REQUEST; + return NULL; + } + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is correct + if( + !(root->ns + && xstreq(root->ns->href, "DAV:") + && xstreq(root->name, "lockinfo"))) + { + *error = LOCK_PARSER_NO_LOCKINFO; + xmlFreeDoc(doc); + return NULL; + } + + WebdavLockScope lockscope = WEBDAV_LOCK_SCOPE_UNKNOWN; + WebdavLockType locktype = WEBDAV_LOCK_TYPE_UNKNOWN; + WSXmlNode *owner = NULL; + + int ret = 0; + while(node && !ret) { + if( + node->type == XML_ELEMENT_NODE + && node->ns + && xstreq(node->ns->href, "DAV:")) + { + char *name = (char*)node->name; + if(xstreq(name, "lockscope")) { + xmlNode *s = node->children; + while(s) { + if( + s->type == XML_ELEMENT_NODE + && s->ns + && xstreq(s->ns->href, "DAV:")) + { + if(xstreq(s->name, "exclusive")) { + lockscope = WEBDAV_LOCK_EXCLUSIVE; + } else if(xstreq(s->name, "shared")) { + lockscope = WEBDAV_LOCK_SHARED; + } else { + // don't ignore unknown lockscope + *error = LOCK_PARSER_UNKNOWN_ELEMENT; + ret = 1; + break; + } + } + s = s->next; + } + } else if(xstreq(name, "locktype")) { + xmlNode *t = node->children; + while(t) { + if( + t->type == XML_ELEMENT_NODE + && t->ns + && xstreq(t->ns->href, "DAV:")) + { + if(xstreq(t->name, "write")) { + locktype = WEBDAV_LOCK_WRITE; + } else { + *error = LOCK_PARSER_UNKNOWN_ELEMENT; + ret = 1; + break; + } + } + t = t->next; + } + } else if(xstreq(name, "owner")) { + owner = node->children; + } + } + node = node->next; + } + + if(ret) { + xmlFreeDoc(doc); + return NULL; + } + + WebdavLockRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavLockRequest)); + if(!request) { + *error = LOCK_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->doc = doc; + + if(locktype == WEBDAV_LOCK_TYPE_UNKNOWN) { + locktype = WEBDAV_LOCK_WRITE; + } + if(lockscope == WEBDAV_LOCK_SCOPE_UNKNOWN) { + lockscope = WEBDAV_LOCK_EXCLUSIVE; + } + request->scope = lockscope; + request->type = locktype; + request->owner = owner; + return request; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/requestparser.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,106 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef REQUESTPARSER_H +#define REQUESTPARSER_H + +#include "../public/webdav.h" + +#include <libxml/tree.h> +#include <ucx/buffer.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROPFIND_PARSER_OK 0 +#define PROPFIND_PARSER_NO_PROPFIND 1 +#define PROPFIND_PARSER_NO_PROPERTIES 2 +#define PROPFIND_PARSER_INVALID_REQUEST 3 +#define PROPFIND_PARSER_OOM 4 +#define PROPFIND_PARSER_ERROR 5 + +#define PROPPATCH_PARSER_OK 0 +#define PROPPATCH_PARSER_NO_PROPERTYUPDATE 1 +#define PROPPATCH_PARSER_NO_PROPERTIES 2 +#define PROPPATCH_PARSER_INVALID_REQUEST 3 +#define PROPPATCH_PARSER_DUPLICATE 4 +#define PROPPATCH_PARSER_OOM 5 +#define PROPPATCH_PARSER_ERROR 6 + +#define LOCK_PARSER_OK 0 +#define LOCK_PARSER_NO_LOCKINFO 1 +#define LOCK_PARSER_INVALID_REQUEST 2 +#define LOCK_PARSER_UNKNOWN_ELEMENT 3 +#define LOCK_PARSER_OOM 4 +#define LOCK_PARSER_ERROR 5 + + +WebdavProperty* prop_create( + pool_handle_t *pool, + WSNamespace *ns, + const char *name); + +WebdavPropfindRequest* propfind_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + +WebdavProppatchRequest* proppatch_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + +WebdavProppatchRequest* webdav_parse_set( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + const char *rootns, + const char *rootname, + WSBool allowremove, + int *error); + +WebdavLockRequest* lock_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + + +#ifdef __cplusplus +} +#endif + +#endif /* REQUESTPARSER_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/search.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,33 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "search.h" + +int webdav_search (pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/search.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SEARCH_H +#define SEARCH_H + +#include "../public/webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int webdav_search (pblock *pb, Session *sn, Request *rq); + + +#ifdef __cplusplus +} +#endif + +#endif /* SEARCH_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/versioning.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "versioning.h" + +int webdav_version_control(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_checkout(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_checkin(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_uncheckout(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_update(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_label(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_merge(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/versioning.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef VERSIONING_H +#define VERSIONING_H + +#include "../public/webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int webdav_version_control(pblock *pb, Session *sn, Request *rq); +int webdav_checkout(pblock *pb, Session *sn, Request *rq); +int webdav_checkin(pblock *pb, Session *sn, Request *rq); +int webdav_uncheckout(pblock *pb, Session *sn, Request *rq); +int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq); +int webdav_update(pblock *pb, Session *sn, Request *rq); +int webdav_label(pblock *pb, Session *sn, Request *rq); +int webdav_merge(pblock *pb, Session *sn, Request *rq); + + +#ifdef __cplusplus +} +#endif + +#endif /* VERSIONING_H */ +
--- a/src/server/webdav/webdav.c Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/webdav/webdav.c Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,6 +30,1080 @@ #include <stdlib.h> #include <string.h> +#include <ucx/buffer.h> +#include <ucx/list.h> + #include "webdav.h" +#include "search.h" +#include "versioning.h" +#include "multistatus.h" +#include "requestparser.h" +#include "operation.h" +#include "../util/pblock.h" +#include "../util/util.h" +#include "../daemon/session.h" +#include "../daemon/http.h" +#include "../daemon/protocol.h" +#include "../daemon/vfs.h" + +static UcxMap *method_handler_map; + +static WebdavBackend default_backend; + +static WSNamespace dav_namespace; + +static WebdavProperty dav_resourcetype_empty; +static WebdavProperty dav_resourcetype_collection; +static WSXmlData dav_resourcetype_collection_value; + +#define WEBDAV_RESOURCE_TYPE_COLLECTION "<D:collection/>" + +static void init_default_backend(void) { + memset(&default_backend, 0, sizeof(WebdavBackend)); + default_backend.propfind_init = default_propfind_init; + default_backend.propfind_do = default_propfind_do; + default_backend.propfind_finish = default_propfind_finish; + default_backend.proppatch_do = default_proppatch_do; + default_backend.proppatch_finish = default_proppatch_finish; + default_backend.opt_mkcol = NULL; + default_backend.opt_delete = NULL; + default_backend.settings = WS_WEBDAV_PROPFIND_USE_VFS; +} + +int webdav_init(pblock *pb, Session *sn, Request *rq) { + init_default_backend(); + + method_handler_map = ucx_map_new(64); + + ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options); + ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind); + ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch); + ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol); + ucx_map_cstr_put(method_handler_map, "POST", webdav_post); + ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete); + ucx_map_cstr_put(method_handler_map, "PUT", webdav_put); + ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy); + ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move); + ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock); + ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock); + ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report); + ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl); + + ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search); + + ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control); + ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout); + ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin); + ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout); + ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace); + ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update); + ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label); + ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge); + + dav_namespace.href = (xmlChar*)"DAV:"; + dav_namespace.prefix = (xmlChar*)"D"; + + dav_resourcetype_empty.namespace = &dav_namespace; + dav_resourcetype_empty.name = "resourcetype"; + + dav_resourcetype_collection.namespace = &dav_namespace; + dav_resourcetype_collection.name = "resourcetype"; + dav_resourcetype_collection.value.data = &dav_resourcetype_collection_value; + dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA; + dav_resourcetype_collection_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION; + dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1; + + return REQ_PROCEED; +} + + +int webdav_service(pblock *pb, Session *sn, Request *rq) { + if(!method_handler_map) { + log_ereport(LOG_FAILURE, "WebDAV module not initialized"); + protocol_status(sn, rq, 500, NULL); + return REQ_ABORTED; + } + char *method = pblock_findkeyval(pb_key_method, rq->reqpb); + + FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method); + if(!saf) { + return REQ_NOACTION; + } + + return saf(pb, sn, rq); +} + +UcxBuffer* rqbody2buffer(Session *sn, Request *rq) { + if(!sn->inbuf) { + //request body required, set http response code + protocol_status(sn, rq, 400, NULL); + return NULL; + } + + UcxBuffer *buf = ucx_buffer_new( + NULL, + sn->inbuf->maxsize, + UCX_BUFFER_AUTOEXTEND); + if(!buf) { + protocol_status(sn, rq, 500, NULL); + return NULL; + } + + char in[2048]; + int r; + while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { + if(ucx_buffer_write(in, 1, r, buf) != r) { + protocol_status(sn, rq, 500, NULL); + ucx_buffer_free(buf); + return NULL; + } + } + + return buf; +} + +int webdav_options(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_propfind(pblock *pb, Session *sn, Request *rq) { + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + UcxBuffer *reqbody = rqbody2buffer(sn, rq); + if(!reqbody) { + return REQ_ABORTED; + } + + UcxAllocator *a = session_get_allocator(sn); + + int error = 0; + WebdavPropfindRequest *propfind = propfind_parse( + sn, + rq, + reqbody->space, + reqbody->size, + &error); + ucx_buffer_free(reqbody); + if(!propfind) { + switch(error) { + // TODO: handle all errors + default: return REQ_ABORTED; + } + } + + WebdavBackend *dav = rq->davCollection ? + rq->davCollection : &default_backend; + + + // requested uri and path + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + // The multistatus response object contains responses for all + // requested resources. At the end the Multistatus object will be + // serialized to xml + Multistatus *ms = multistatus_response(sn, rq); + if(!ms) { + return REQ_ABORTED; + } + + + int ret = webdav_propfind_do(dav, propfind, (WebdavResponse*)ms, NULL, path, uri); + + // if propfind was successful, send the result to the client + if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) { + ret = REQ_ABORTED; + // TODO: log error + } else { + // TODO: error response + } + + // cleanup + if(propfind->doc) { + xmlFreeDoc(propfind->doc); + } + + return ret; +} + +/* + * Initializes Backend Chain + * + * Calls propfind_init of each Backend and generates a list of custom + * WebdavPropfindRequest objects for each backend + */ +int webdav_propfind_init( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + const char *path, + UcxList **out_req) +{ + pool_handle_t *pool = propfind->sn->pool; + UcxAllocator *a = session_get_allocator(propfind->sn); + + // list of individual WebdavPropfindRequest objects for each Backend + UcxList *requestObjects = NULL; + + // new properties after init, start with clone of original plist + WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties); + size_t newPropCount = propfind->propcount; + + // Call propfind_init for each Backend + // propfind_init can return a new property list, which + // will be passed to the next backend + + WebdavBackend *davList = dav; + while(davList) { + // create WebdavPropfindRequest copy + WebdavPropfindRequest *pReq = pool_malloc( + pool, + sizeof(WebdavPropfindRequest)); + memcpy(pReq, propfind, sizeof(WebdavPropfindRequest)); + // use new plist after previous init (or orig. plist in the first run) + pReq->properties = newProp; + pReq->propcount = newPropCount; + + // add new WebdavPropfindRequest object to list for later use + requestObjects = ucx_list_append_a(a, requestObjects, pReq); + if(!requestObjects) { + return REQ_ABORTED; // OOM + } + + // create plist copy as out-plist for init + newProp = webdav_plist_clone(pool, newProp); + + // run init: this can generate a new properties list (newProp) + // which will be passed to the next backend + if(davList->propfind_init(pReq, path, &newProp)) { + return REQ_ABORTED; + } + + newPropCount = webdav_plist_size(newProp); + + davList = davList->next; + } + + *out_req = requestObjects; + return REQ_PROCEED; +} + +int webdav_propfind_do( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFSContext *vfs, + char *path, + char *uri) +{ + Session *sn = propfind->sn; + Request *rq = propfind->rq; + + // VFS settings are only taken from the first backend + uint32_t settings = dav->settings; + + // list of individual WebdavPropfindRequest objects for each Backend + UcxList *requestObjects = NULL; + + // Initialize all Webdav Backends + if(webdav_propfind_init(dav, propfind, path, &requestObjects)) { + return REQ_ABORTED; + } + + WebdavOperation *op = webdav_create_propfind_operation( + sn, + rq, + dav, + propfind->properties, + requestObjects, + response); + + // some Backends can list all children by themselves, but some + // require the VFS for this + WSBool usevfs = (settings & WS_WEBDAV_PROPFIND_USE_VFS) + == WS_WEBDAV_PROPFIND_USE_VFS; + struct stat s; + struct stat *statptr = NULL; + + if(usevfs && !vfs) { + vfs = vfs_request_context(sn, rq); + if(!vfs) { + return REQ_ABORTED; + } + + if(vfs_stat(vfs, path, &s)) { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + statptr = &s; + if(!S_ISDIR(s.st_mode)) { + // the file is not a directory, therefore we don't need the VFS + usevfs = FALSE; + } + } + if(propfind->depth == 0) { + usevfs = FALSE; + } + + int ret = REQ_PROCEED; + + // create WebdavResource object for requested resource + if(!webdav_op_propfind_begin(op, uri, NULL, statptr)) { + // propfind for the requested resource was successful + + // usevfsdir is TRUE if + // the webdav backend has not disabled vfs usage + // the file is a directory + // depth is not 0 + // in this case we need to execute propfind_do for all children + if(usevfs) { + if(webdav_op_propfind_children(op, vfs, uri, path)) { + ret = REQ_ABORTED; + } + } + } + + // finish the propfind request + // this function should cleanup all resources, therefore we execute it + // even if a previous function failed + if(webdav_op_propfind_finish(op)) { + // TODO: log error + ret = REQ_ABORTED; + } + + return ret; +} + + +int webdav_proppatch(pblock *pb, Session *sn, Request *rq) { + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + UcxBuffer *reqbody = rqbody2buffer(sn, rq); + if(!reqbody) { + return REQ_ABORTED; + } + + int error = 0; + WebdavProppatchRequest *proppatch = proppatch_parse( + sn, + rq, + reqbody->space, + reqbody->size, + &error); + ucx_buffer_free(reqbody); + if(!proppatch) { + switch(error) { + // TODO: handle all errors + default: return REQ_ABORTED; + } + } + + WebdavBackend *dav = rq->davCollection ? + rq->davCollection : &default_backend; + + // requested uri and path + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + // The multistatus response object contains responses for all + // requested resources. At the end the Multistatus object will be + // serialized to xml + Multistatus *ms = multistatus_response(sn, rq); + if(!ms) { + return REQ_ABORTED; + } + ms->proppatch = TRUE; + + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + WebdavOperation *op = webdav_create_proppatch_operation( + sn, + rq, + dav, + proppatch, + response); + + int ret = REQ_PROCEED; + + // Execute proppatch + if(webdav_op_proppatch(op, uri, path)) { + ret = REQ_ABORTED; + } + + // send response + if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) { + ret = REQ_ABORTED; + // TODO: log error + } else { + // TODO: error response + } + + // cleanup + xmlFreeDoc(proppatch->doc); + + return ret; +} + +int webdav_mkcol(pblock *pb, Session *sn, Request *rq) { + WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE); + if(!op) { + return REQ_ABORTED; + } + + int ret = webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR); + + return ret; +} + +int webdav_post(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +typedef struct DeleteFile { + char *path; + struct stat s; +} DeleteFile; + +typedef struct DeleteLists { + UcxAllocator *a; + UcxList *dirs_begin; + UcxList *dirs_end; + UcxList *files_begin; + UcxList *files_end; +} DeleteOp; + +static int deletelist_add( + VFSContext *vfs, + const char *href, + const char *path, + VFSDir *parent, + struct stat *s, + void *userdata) +{ + DeleteOp *op = userdata; + + // create object for this file + DeleteFile *file = almalloc(op->a, sizeof(DeleteFile)); + if(!file) { + return 1; + } + file->path = sstrdup_a(op->a, sstr((char*)path)).ptr; + if(!file->path) { + return 1; + } + file->s = *s; + + // determine which list to use + UcxList **begin; + UcxList **end; + if(S_ISDIR(s->st_mode)) { + begin = &op->dirs_begin; + end = &op->dirs_end; + } else { + begin = &op->files_begin; + end = &op->files_end; + } + + // add file to list + UcxList *elm = ucx_list_append_a(op->a, NULL, file); + if(!elm) { + alfree(op->a, file->path); // at least do some cleanup, although it + alfree(op->a, file); // isn't really necessary + return 1; + } + if(*begin == NULL) { + *begin = elm; + *end = elm; + } else { + ucx_list_concat(*end, elm); + *end = elm; + } + + return 0; +} + +static int webdav_delete_collection(WebdavVFSOperation *op) +{ + DeleteOp del; + ZERO(&del, sizeof(DeleteOp)); + del.a = session_get_allocator(op->sn); + + // get a list of all files + if(webdav_op_iterate_children(op->vfs, -1, NULL, op->path, + deletelist_add, &del)) + { + return 1; + } + + // add root to list of dir list + DeleteFile root; + root.path = op->path; + root.s = *op->stat; + UcxList root_elm; + root_elm.data = &root; + root_elm.prev = NULL; + root_elm.next = del.dirs_begin; + + if(del.dirs_begin) { + del.dirs_begin->prev = &root_elm; + del.dirs_begin = &root_elm; + } else { + del.dirs_begin = &root_elm; + del.dirs_end = &root_elm; + } + + // delete files first + UCX_FOREACH(elm, del.files_begin) { + DeleteFile *file = elm->data; + WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s); + if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) { + return 1; + } + } + + // delete directories, reverse order + for(UcxList *elm=del.dirs_end;elm;elm=elm->prev) { + DeleteFile *file = elm->data; + WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s); + if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) { + return 1; + } + } + + return 0; +} + +int webdav_delete(pblock *pb, Session *sn, Request *rq) { + WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE); + if(!op) { + return REQ_ABORTED; + } + + // stat to find out if the resource is a collection + struct stat s; + if(vfs_stat(op->vfs, op->path, &s)) { + sys_set_error_status(op->vfs); + return REQ_ABORTED; + } + op->stat = &s; + + int ret; + if(S_ISDIR(s.st_mode)) { + ret = webdav_delete_collection(op); + } else { + ret = webdav_vfs_op_do(op, WEBDAV_VFS_DELETE); + } + + return ret; +} + +int webdav_put(pblock *pb, Session *sn, Request *rq) { + char *path = pblock_findkeyval(pb_key_path, rq->vars); + + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); + return REQ_ABORTED; + } + + struct stat s; + int create_file = 0; + if(vfs_stat(vfs, path, &s)) { + if(vfs->vfs_errno == ENOENT) { + create_file = O_CREAT; + } else { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + } + + if(S_ISDIR(s.st_mode)) { + // PUT on collections is not allowed + protocol_status(sn, rq, PROTOCOL_METHOD_NOT_ALLOWED, NULL); + return REQ_ABORTED; + } + + SYS_FILE fd = vfs_open(vfs, path, O_RDONLY | create_file); + if(!fd) { + // if it fails, vfs_open sets http status code + return REQ_ABORTED; + } + + // TODO: check permissions, lock, ... + + // all checks done + + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + char in[4096]; + int r; + while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { + int w = 0; + while(w < r) { + w += system_fwrite(fd, in, r); + } + } + + system_fclose(fd); + + int status = create_file ? PROTOCOL_CREATED : PROTOCOL_NO_CONTENT; + protocol_status(sn, rq, status, NULL); + + return REQ_PROCEED; +} + +int webdav_copy(pblock *pb, Session *sn, Request *rq) { + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + char *destination = pblock_findval("destination", rq->headers); + if(!destination) { + protocol_status(sn, rq, PROTOCOL_BAD_REQUEST, NULL); + return REQ_ABORTED; + } + + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); + return REQ_ABORTED; + } + + struct stat src_s; + if(vfs_stat(vfs, path, &src_s)) { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + + // TODO: if src is a directory, make sure the uri has a trailing path separator + + + return REQ_ABORTED; +} + +int webdav_move(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_lock(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_unlock(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_report(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_acl(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + + + +/* ------------------------ default webdav backend ------------------------ */ + +int default_propfind_init( + WebdavPropfindRequest *rq, + const char* path, + WebdavPList **outplist) +{ + DefaultWebdavData *data = pool_malloc( + rq->sn->pool, + sizeof(DefaultWebdavData)); + if(!data) { + return 1; + } + rq->userdata = data; + + data->vfsproperties = webdav_vfs_properties(outplist, TRUE, 0); + + return 0; +} + +int default_propfind_do( + WebdavPropfindRequest *request, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + DefaultWebdavData *data = request->userdata; + + // add all requested vfs properties like getcontentlength ... + if(webdav_add_vfs_properties( + resource, + request->sn->pool, + data->vfsproperties, + s)) + { + return 1; + } + + return 0; +} + +int default_propfind_finish(WebdavPropfindRequest *rq) { + return 0; +} + +int default_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **setInOut, + WebdavPList **removeInOut) +{ + return 0; +} + +int default_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + return 0; +} + + +/* ------------------------------ public API ------------------------------ */ + +int webdav_getdepth(Request *rq) { + char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers); + int depth = 0; + if(depth_str) { + size_t dlen = strlen(depth_str); + if(!memcmp(depth_str, "infinity", dlen)) { + depth = -1; + } else if(dlen == 1 && depth_str[0] == '1') { + depth = 1; + } + } + return depth; +} + +int webdav_plist_add( + pool_handle_t *pool, + WebdavPList **begin, + WebdavPList **end, + WebdavProperty *prop) +{ + WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList)); + if(!elm) { + return 1; + } + elm->prev = *end; + elm->next = NULL; + elm->property = prop; + + if(!*begin) { + *begin = elm; + *end = elm; + return 0; + } + + (*end)->next = elm; + *end = elm; + + return 0; +} + +WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) { + return webdav_plist_clone_s(pool, list, NULL); +} + +WebdavPList* webdav_plist_clone_s( + pool_handle_t *pool, + WebdavPList *list, + size_t *newlen) +{ + WebdavPList *new_list = NULL; // start of the new list + WebdavPList *new_list_end = NULL; // end of the new list + + size_t len = 0; + + WebdavPList *elm = list; + while(elm) { + // copy list item + WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList)); + if(!new_elm) { + if(newlen) *newlen = 0; + return NULL; + } + new_elm->property = elm->property; // new list contains original ptr + new_elm->prev = new_list_end; + new_elm->next = NULL; + + if(new_list_end) { + new_list_end->next = new_elm; + } else { + new_list = new_elm; + } + new_list_end = new_elm; + + len++; + elm = elm->next; + } + + if(newlen) *newlen = len; + return new_list; +} + +size_t webdav_plist_size(WebdavPList *list) { + size_t count = 0; + WebdavPList *elm = list; + while(elm) { + count++; + elm = elm->next; + } + return count; +} + +WebdavPListIterator webdav_plist_iterator(WebdavPList **list) { + WebdavPListIterator i; + i.list = list; + i.cur = NULL; + i.next = *list; + i.index = 0; + return i; +} + +int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur) { + if(i->cur) { + i->index++; + } + + i->cur = i->next; + i->next = i->cur ? i->cur->next : NULL; + *cur = i->cur; + + return i->cur != NULL; +} + +void webdav_plist_iterator_remove_current(WebdavPListIterator *i) { + WebdavPList *cur = i->cur; + if(cur->prev) { + cur->prev->next = cur->next; + if(cur->next) { + cur->next->prev = cur->prev; + } + } else { + *i->list = cur->next; + if(cur->next) { + cur->next->prev = NULL; + } + } +} + +int webdav_nslist_add( + pool_handle_t *pool, + WebdavNSList **begin, + WebdavNSList **end, + WSNamespace *ns) +{ + // same as webdav_plist_add but with different type + WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList)); + if(!elm) { + return 1; + } + elm->prev = *end; + elm->next = NULL; + elm->namespace = ns; + + if(!*begin) { + *begin = elm; + *end = elm; + return 0; + } + + (*end)->next = elm; + *end = elm; + + return 0; +} + + +WSNamespace* webdav_dav_namespace(void) { + return &dav_namespace; +} + +WebdavProperty* webdav_dav_property( + pool_handle_t *pool, + const char *name) +{ + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + if(!property) { + return NULL; + } + memset(property, 0, sizeof(WebdavProperty)); + + property->namespace = &dav_namespace; + property->name = name; + return property; +} + +int webdav_property_set_value( + WebdavProperty *p, + pool_handle_t *pool, + char *value) +{ + WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode)); + if(!node) { + return 1; + } + ZERO(node, sizeof(WSXmlNode)); + + node->content = (xmlChar*)value; + node->type = XML_TEXT_NODE; + + p->value.node = node; + p->vtype = WS_VALUE_XML_NODE; + return 0; +} + +WebdavVFSProperties webdav_vfs_properties( + WebdavPList **plistInOut, + WSBool removefromlist, + uint32_t flags) +{ + WebdavVFSProperties ret; + ZERO(&ret, sizeof(WebdavVFSProperties)); + + WSBool etag = 1; + WSBool creationdate = 1; + + WebdavPListIterator i = webdav_plist_iterator(plistInOut); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + WSNamespace *ns = cur->property->namespace; + if(ns && !strcmp((const char*)ns->href, "DAV:")) { + const char *name = cur->property->name; + WSBool remove_prop = TRUE; + if(!strcmp(name, "getlastmodified")) { + ret.getlastmodified = 1; + } else if(!strcmp(name, "getcontentlength")) { + ret.getcontentlength = 1; + } else if(!strcmp(name, "resourcetype")) { + ret.getresourcetype = 1; + } else if(etag && !strcmp(name, "getetag")) { + ret.getetag = 1; + } else if(creationdate && !strcmp(name, "creationdate")) { + ret.creationdate = 1; + } else { + remove_prop = FALSE; + } + + if(remove_prop) { + webdav_plist_iterator_remove_current(&i); + } + } + } + + return ret; +} + +static inline int w_addprop( + WebdavResource *res, + pool_handle_t *pool, + const char *name, + char *value) +{ + WebdavProperty *p = webdav_dav_property(pool, name); + if(!p) { + return 1; + } + if(webdav_property_set_value(p, pool, value)) { + return 1; + } + return res->addproperty(res, p, 200); +} + +int webdav_add_vfs_properties( + WebdavResource *res, + pool_handle_t *pool, + WebdavVFSProperties properties, + struct stat *s) +{ + if(properties.getresourcetype) { + if(S_ISDIR(s->st_mode)) { + res->addproperty(res, &dav_resourcetype_collection, 200); + } else { + res->addproperty(res, &dav_resourcetype_empty, 200); + } + } + if(properties.getcontentlength) { + char *buf = pool_malloc(pool, 64); + if(!buf) { + return 1; + } + uint64_t contentlength = s->st_size; + snprintf(buf, 64, "%" PRIu64 "\0", contentlength); + if(w_addprop(res, pool, "getcontentlength", buf)) { + return 1; + } + } + if(properties.getlastmodified) { + char *buf = pool_malloc(pool, HTTP_DATE_LEN+1); + if(!buf) { + return 1; + } + buf[HTTP_DATE_LEN] = 0; + + struct tm mtms; + struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms); + + if(mtm) { + strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm); + if(w_addprop(res, pool, "getlastmodified", buf)) { + return 1; + } + } else { + return 1; + } + } + if(properties.creationdate) { + // TODO + } + if(properties.getetag) { + char *buf = pool_malloc(pool, 96); + if(!buf) { + return 1; + } + snprintf(buf, + 96, + "\"%x-%x\"\0", + (int)s->st_size, + (int)s->st_mtim.tv_sec); + if(w_addprop(res, pool, "getetag", buf)) { + return 1; + } + } + + return 0; +}
--- a/src/server/webdav/webdav.h Mon Aug 24 19:19:56 2020 +0200 +++ b/src/server/webdav/webdav.h Tue Aug 25 12:07:56 2020 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,7 +30,6 @@ #define WEBDAV_H #include "../public/webdav.h" -#include "../util/strbuf.h" #include <ucx/map.h> #include <ucx/list.h> @@ -38,8 +37,82 @@ #ifdef __cplusplus extern "C" { #endif + + + +typedef struct DefaultWebdavData { + WebdavVFSProperties vfsproperties; +} DefaultWebdavData; + + +int webdav_init(pblock *pb, Session *sn, Request *rq); + +int webdav_service(pblock *pb, Session *sn, Request *rq); + +/* + * returns a buffer containing the request body + * + * this function sets an http response code in case of an error + * or missing request body + */ +UcxBuffer* rqbody2buffer(Session *sn, Request *rq); +int webdav_options(pblock *pb, Session *sn, Request *rq); + +int webdav_propfind(pblock *pb, Session *sn, Request *rq); + +int webdav_propfind_init( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + const char *path, + UcxList **out_req); + +int webdav_propfind_do( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFSContext *vfs, + char *path, + char *uri); + + +int webdav_proppatch(pblock *pb, Session *sn, Request *rq); +int webdav_mkcol(pblock *pb, Session *sn, Request *rq); +int webdav_post(pblock *pb, Session *sn, Request *rq); +int webdav_delete(pblock *pb, Session *sn, Request *rq); +int webdav_put(pblock *pb, Session *sn, Request *rq); +int webdav_copy(pblock *pb, Session *sn, Request *rq); +int webdav_move(pblock *pb, Session *sn, Request *rq); +int webdav_lock(pblock *pb, Session *sn, Request *rq); +int webdav_unlock(pblock *pb, Session *sn, Request *rq); +int webdav_report(pblock *pb, Session *sn, Request *rq); +int webdav_acl(pblock *pb, Session *sn, Request *rq); +int webdav_search (pblock *pb, Session *sn, Request *rq); + + +int default_propfind_init( + WebdavPropfindRequest *rq, + const char* path, + WebdavPList **outplist); +int default_propfind_do( + WebdavPropfindRequest *request, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s); +int default_propfind_finish(WebdavPropfindRequest *rq); +int default_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **setInOut, + WebdavPList **removeInOut); +int default_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit); #ifdef __cplusplus }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/xml.c Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,564 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <ucx/string.h> +#include <ucx/map.h> + +#include "../util/util.h" + +#include "xml.h" + +/***************************************************************************** + * Utility functions + *****************************************************************************/ + +/* + * generates a string key for an xml namespace + * format: prefix '\0' href + */ +static sstr_t xml_namespace_key(UcxAllocator *a, WSNamespace *ns) { + sstr_t key = sstrcat_a(a, 3, + ns->prefix ? sstr((char*)ns->prefix) : S("\0"), + S("\0"), + sstr((char*)ns->href)); + return key; +} + + +/***************************************************************************** + * Public functions + *****************************************************************************/ + +/* ------------------------ wsxml_iterator ------------------------ */ + +typedef struct StackElm { + WSXmlNode *node; // list of nodes + //WSXmlNode *parent; // if not NULL, call endcb after node->next is NULL + int endonly; + struct StackElm *next; +} StackElm; + +#define STACK_PUSH(stack, elm) if(stack) { elm->next = stack; } stack = elm; + +int wsxml_iterator( + pool_handle_t *pool, + WSXmlNode *node, + wsxml_func begincb, + wsxml_func endcb, + void *udata) +{ + if(!node) { + return 0; + } + + StackElm *stack = pool_malloc(pool, sizeof(StackElm)); + if(!stack) { + return 1; // OOM + } + stack->next = NULL; + stack->node = node; + stack->endonly = 0; + //stack->parent = NULL; + + int ret = 0; + int br = 0; + while(stack) { + StackElm *cur = stack; + WSXmlNode *xmlnode = cur->node; // get top stack element + stack = cur->next; // and remove it + cur->next = NULL; + + while(xmlnode && !cur->endonly) { + // element begin callback + if(begincb(xmlnode, udata)) { + br = 1; + break; // I don't like break with labels - is this wrong? + } + + if(xmlnode->children) { + // put the children on the stack + // the next stack iteration will process the children + StackElm *newelm = pool_malloc(pool, sizeof(StackElm)); + if(!newelm) { + ret = 1; + br = 1; + break; + } + newelm->next = NULL; + newelm->node = xmlnode->children; + // setting the parent will make sure endcb will be called + // for the current xmlnode after all children are processed + //newelm->parent = xmlnode; + newelm->endonly = 0; + + // if xmlnode->next is not NULL, there are still nodes at + // this level, therefore we have to put these also on the + // stack + // this way, the remaining nodes are processed after all + // children and the end tag are processed + if(xmlnode->next) { + StackElm *nextelm = pool_malloc(pool, sizeof(StackElm)); + if(!nextelm) { + ret = 1; + br = 1; + break; + } + nextelm->node = xmlnode->next; + nextelm->next = NULL; + nextelm->endonly = 0; + STACK_PUSH(stack, nextelm); + } + + // we have to put the end tag of the current element + // on the stack to ensure endcb is called for the current + // element, after all children are processed + // reuse cur + cur->node = xmlnode; + cur->endonly = 1; + STACK_PUSH(stack, cur); + + cur = NULL; + + // now we can put the children on the stack + STACK_PUSH(stack, newelm); + // break, because we don't want to process xmlnode->next now + break; + } else { + // no children means, the end callback can be called directly + // after the begin callback (no intermediate nodes) + cur->node = NULL; + if(endcb(xmlnode, udata)) { + br = 1; + break; + } + } + + // continue with next node at this level + xmlnode = xmlnode->next; + } + if(br) { + break; // break because of an error + } + + if(cur && cur->node) { + //xmlNode *endNode = cur->parent ? cur->parent : cur->node; + xmlNode *endNode = cur->node; + if(endcb(endNode, udata)) { + break; + } + pool_free(pool, cur); + } + } + + // free all remaining elements + StackElm *elm = stack; + while(elm) { + StackElm *next = elm->next; + pool_free(pool, elm); + elm = next; + } + + return ret; +} + +/* ------------------- wsxml_get_required_namespaces ------------------- */ + +typedef struct WSNsCollector { + UcxAllocator *a; + UcxMap *nsmap; + WebdavNSList *def; + int error; +} WSNsCollector; + +static int nslist_node_begin(xmlNode *node, void *userdata) { + WSNsCollector *col = userdata; + // namespace required for all elements + if(node->type == XML_ELEMENT_NODE && node->ns) { + // we create a list of unique prefix-href namespaces by putting + // all namespaces in a map + sstr_t nskey = xml_namespace_key(col->a, node->ns); + if(!nskey.ptr) { + col->error = 1; + return 1; + } + if(ucx_map_sstr_put(col->nsmap, nskey, node->ns)) { + col->error = 1; + return 1; + } + + // collect all namespace definitions for removing these namespaces + // from col->nsmap later + WSNamespace *def = node->nsDef; + while(def) { + WebdavNSList *newdef = col->a->malloc( + col->a->pool, sizeof(WebdavNSList)); + if(!newdef) { + col->error = 1; + return 1; + } + newdef->namespace = def; + newdef->prev = NULL; + newdef->next = NULL; + // prepend newdef to the list + if(col->def) { + newdef->next = col->def; + col->def->prev = newdef; + } + col->def = newdef; + + // continue with next namespace definition + def = def->next; + } + } + return 0; +} + +static int nslist_node_end(xmlNode *node, void *userdata) { + return 0; +} + +WebdavNSList* wsxml_get_required_namespaces( + pool_handle_t *pool, + WSXmlNode *node, + int *error) +{ + if(error) *error = 0; + + UcxAllocator a = util_pool_allocator(pool); + UcxMap *nsmap = ucx_map_new_a(&a, 16); + if(!nsmap) { + if(error) *error = 1; + return NULL; + } + + WSNsCollector col; + col.a = &a; + col.nsmap = nsmap; + col.def = NULL; + + // iterate over all xml elements + // this will fill the hashmap with all namespaces + // all namespace definitions are added to col.def + WebdavNSList *list = NULL; + WebdavNSList *end = NULL; + if(wsxml_iterator(pool, node, nslist_node_begin, nslist_node_end, &col)) { + if(error) *error = 1; + } else { + // remove all namespace definitions from the map + // what we get is a map that contains all missing namespace definitions + WebdavNSList *def = col.def; + while(def) { + sstr_t nskey = xml_namespace_key(&a, def->namespace); + if(!nskey.ptr) { + if(error) *error = 1; + break; + } + ucx_map_sstr_remove(nsmap, nskey); + def = def->next; + } + + // convert nsmap to a list + UcxMapIterator i = ucx_map_iterator(nsmap); + WSNamespace *ns; + UCX_MAP_FOREACH(key, ns, i) { + WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList)); + if(!newelm) { + if(error) *error = 1; + list = NULL; + break; + } + newelm->namespace = ns; + newelm->next = NULL; + newelm->prev = end; // NULL or the end of list + if(end) { + end->next = newelm; // append new element + } else { + list = newelm; // start new list + } + end = newelm; + } + } + + ucx_map_free(nsmap); + return list; +} + + +/***************************************************************************** + * Non public functions + *****************************************************************************/ + +typedef struct XmlWriter { + /* + * Memory pool for temp memory allocations + */ + pool_handle_t *pool; + + /* + * Buffered output stream + */ + Writer *out; + + /* + * Map for all previously defined namespaces + * key: (char*) namespace prefix + * value: WSNamespace* + */ + UcxMap *namespaces; + + /* + * Should namespace definitions be created + */ + WSBool define_namespaces; +} XmlWriter; + +/* + * Serialize an XML text node + * This replaces some special characters with entity refs + * type: 0 = element text, 1 = attribute text + */ +static void xml_ser_text(Writer *out, int type, const char *text) { + size_t start = 0; + size_t i; + sstr_t entityref = { NULL, 0 }; + for(i=0;text[i]!='\0';i++) { + char c = text[i]; + if(c == '&') { + entityref = S("&"); + } else if(type == 0) { + if(c == '<') { + entityref = S("<"); + } else if(c == '>') { + entityref = S(">"); + } + } else { + if(c == '\"') { + entityref = S("""); + } else if(c == '\'') { + entityref = S("'"); + } + } + + if(entityref.ptr) { + size_t len = i-start; + if(len > 0) { + writer_put(out, text+start, len); + } + writer_puts(out, entityref); + entityref.ptr = NULL; + entityref.length = 0; + start = i+1; + } + } + size_t len = i-start; + if(len > 0) { + writer_put(out, text+start, len); + } +} + +/* + * Serialize an XML element node + */ +static void xml_ser_element(XmlWriter *xw, xmlNode *node) { + Writer *out = xw->out; + writer_putc(out, '<'); + + // write prefix and ':' + if(node->ns && node->ns->prefix) { + writer_puts(out, sstr((char*)node->ns->prefix)); + writer_putc(out, ':'); + } + + // node name + writer_puts(out, sstr((char*)node->name)); + + // namespace definitions + if(xw->define_namespaces) { + xmlNs *nsdef = node->nsDef; + while(nsdef) { + // we define only namespaces without prefix or namespaces + // with prefix, that are not already defined + // xw->namespaces contains all namespace, that were defined + // before xml serialization + if(!nsdef->prefix) { + writer_puts(out, S(" xmlns=\"")); + writer_puts(out, sstr((char*)nsdef->href)); + writer_putc(out, '"'); + } else { + WSNamespace *n = xw->namespaces ? + ucx_map_cstr_get(xw->namespaces, (char*)nsdef->prefix) : + NULL; + if(!n) { + writer_puts(out, S(" xmlns:")); + writer_puts(out, sstr((char*)nsdef->prefix)); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)nsdef->href)); + writer_putc(out, '"'); + } + } + + nsdef = nsdef->next; + } + } + + // attributes + xmlAttr *attr = node->properties; + while(attr) { + // format: ' [<prefix>:]<name>="<value>"' + writer_putc(out, ' '); + // optional namespace + if(attr->ns && attr->ns->prefix) { + writer_puts(out, sstr((char*)attr->ns->prefix)); + writer_putc(out, ':'); + } + // <name>=" + writer_puts(out, sstr((char*)attr->name)); + writer_puts(out, S("=\"")); + // value + xmlNode *value = attr->children; + while(value) { + if(value->content) { + xml_ser_text(out, 1, (const char*)value->content); + } + value = value->next; + } + // trailing quote + writer_putc(out, '"'); + + attr = attr->next; + } + + if(node->children) { + writer_putc(out, '>'); + } else { + writer_puts(out, S("/>")); + } +} + +static int xml_ser_node_begin(xmlNode *node, void *userdata) { + XmlWriter *xw = userdata; + switch(node->type) { + case XML_ELEMENT_NODE: xml_ser_element(xw, node); break; + case XML_ATTRIBUTE_NODE: break; + case XML_TEXT_NODE: { + xml_ser_text(xw->out, 0, (const char*)node->content); + break; + } + case XML_CDATA_SECTION_NODE: { + break; + } + case XML_ENTITY_REF_NODE: break; + case XML_ENTITY_NODE: break; + case XML_PI_NODE: break; + case XML_COMMENT_NODE: break; + case XML_DOCUMENT_NODE: break; + case XML_DOCUMENT_TYPE_NODE: break; + case XML_DOCUMENT_FRAG_NODE: break; + case XML_NOTATION_NODE: break; + case XML_HTML_DOCUMENT_NODE: break; + case XML_DTD_NODE: break; + case XML_ELEMENT_DECL: break; + case XML_ATTRIBUTE_DECL: break; + case XML_ENTITY_DECL: break; + case XML_NAMESPACE_DECL: break; + case XML_XINCLUDE_START: break; + case XML_XINCLUDE_END: break; + default: break; + } + return 0; +} + +static int xml_ser_node_end(xmlNode *node, void *userdata) { + XmlWriter *xw = userdata; + Writer *out = xw->out; + if(node->type == XML_ELEMENT_NODE) { + if(node->children) { + writer_puts(xw->out, S("</")); + // write prefix and ':' + if(node->ns && node->ns->prefix) { + writer_puts(out, sstr((char*)node->ns->prefix)); + writer_putc(out, ':'); + } + // name and close tag + writer_puts(out, sstr((char*)node->name)); + writer_putc(out, '>'); + + } // element was already closed in xml_ser_node_begin + } + return 0; +} + + +static int xml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + WSBool createdefs, + xmlNode *node) +{ + XmlWriter xmlwriter; + xmlwriter.pool = pool; + xmlwriter.out = out; + xmlwriter.namespaces = nsdefs; + xmlwriter.define_namespaces = createdefs; + + // iterate over xml nodes + // this includes node->children and node->next + int err = wsxml_iterator( + pool, + node, + xml_ser_node_begin, + xml_ser_node_end, + &xmlwriter); + if(err) { + return -1; + } + + return out->error; +} + +int wsxml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + xmlNode *node) +{ + return xml_write_nodes(pool, out, nsdefs, TRUE, node); +} + +int wsxml_write_nodes_without_nsdef( + pool_handle_t *pool, + Writer *out, + xmlNode *node) +{ + return xml_write_nodes(pool, out, NULL, FALSE, node); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/xml.h Tue Aug 25 12:07:56 2020 +0200 @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBDAV_XML_H +#define WEBDAV_XML_H + +#include "../public/webdav.h" +#include <libxml/tree.h> + +#include <ucx/map.h> + +#include "../util/writer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Writes the xmlNode, all children and following nodes to the writer 'out'. + */ +int wsxml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + xmlNode *node); + +/* + * Writes the xmlNode, all children and following nodes to the writer 'out' + * without creating any namespace definitions. Therefore all namespaces must + * be already defined and previously written to 'out'. + */ +int wsxml_write_nodes_without_nsdef( + pool_handle_t *pool, + Writer *out, + xmlNode *node); + + +#ifdef __cplusplus +} +#endif + +#endif /* WEBDAV_XML_H */ +