--- a/src/server/webdav/webdav.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/webdav/webdav.c Sat Sep 24 16:26:10 2022 +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,1268 @@ #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" + +/* + * http method fptr mapping + * key: http method name (string) + * value: SAF fptr + */ +static UcxMap *method_handler_map; + +/* + * webdav backend types + * key: backend name (string) + * value: WebdavBackend* + */ +static UcxMap *webdav_type_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; + default_backend.instance = NULL; +} + +int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate) { + WebdavType *webdavType = malloc(sizeof(WebdavType)); + webdavType->init = webdavInit; + webdavType->create = webdavCreate; + return ucx_map_cstr_put(webdav_type_map, name, webdavType); +} + +WebdavType* webdav_get_type(scstr_t dav_class) { + return ucx_map_sstr_get(webdav_type_map, dav_class); +} + +void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error) { + *error = 0; + if(dav_class->init) { + void *initData = dav_class->init(cfg, pool, config); + if(!initData) { + *error = 1; + } + return initData; + } else { + return NULL; + } +} + +WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData) { + WebdavType *webdavType = ucx_map_cstr_get(webdav_type_map, dav_class); + if(!webdavType) { + log_ereport(LOG_MISCONFIG, "webdav_create: unkown dav type %s", dav_class); + return NULL; + } + + return webdavType->create(sn, rq, pb, initData); +} + +static WSBool webdav_is_initialized = FALSE; + +int webdav_init(pblock *pb, Session *sn, Request *rq) { + if(webdav_is_initialized) { + return REQ_NOACTION; + } + webdav_is_initialized = TRUE; + + webdav_type_map = ucx_map_new(8); + if(!webdav_type_map) { + return REQ_ABORTED; + } + + method_handler_map = ucx_map_new(64); + if(!method_handler_map) { + return REQ_ABORTED; + } + + init_default_backend(); + ucx_map_cstr_put(webdav_type_map, "default", &default_backend); + + 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_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION; + dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1; + + 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; + + 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, + const char *uri, + 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; + pReq->dav = davList; + + // 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, uri, &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, uri, &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 = REQ_ABORTED; + if(!webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR)) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, 201, NULL); + protocol_start_response(sn, rq); + ret = REQ_PROCEED; + } else { + int status_code = 500; + if(op->vfs->vfs_errno == EEXIST) { + // 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL. + status_code = 405; + } else if(op->vfs->vfs_errno == ENOENT) { + // 409 (Conflict) - A collection cannot be made at the Request-URI until + // one or more intermediate collections have been created. The server + // MUST NOT create those intermediate collections automatically. + status_code = 409; + } + protocol_status(sn, rq, status_code, NULL); + } + + 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); + } + + // send response + if(ret == REQ_PROCEED) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, 204, NULL); + protocol_start_response(sn, rq); + } else { + protocol_status(sn, rq, 500, NULL); + } + + 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; + } + } else 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_WRONLY | O_TRUNC | 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; + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, status, NULL); + protocol_start_response(sn, rq); + + 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, + const char *href, + 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, rq->allprop, 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; +} + + + +/* ------------------------------ Utils ------------------------------ */ + +UcxKey webdav_property_key(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; +} + + + + +/* ------------------------------ 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_resourcetype_collection(void) { + return &dav_resourcetype_collection; +} + +WebdavProperty* webdav_resourcetype_empty(void) { + return &dav_resourcetype_empty; +} + +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_resource_add_dav_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *name, + const char *str, + size_t len) +{ + WebdavProperty *property = webdav_dav_property(pool, name); + if(!property) { + return 1; + } + + property->name = pool_strdup(pool, name); + if(!property->name) { + return 1; + } + + char *value = pool_malloc(pool, len+1); + if(!value) { + return 1; + } + memcpy(value, str, len); + value[len] = '\0'; + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + return res->addproperty(res, property, 200); +} + +int webdav_resource_add_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *xmlns_prefix, + const char *xmlns_href, + const char *name, + const char *str, + size_t len) +{ + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + if(!property) { + return 1; + } + memset(property, 0, sizeof(WebdavProperty)); + + property->name = pool_strdup(pool, name); + if(!property->name) { + return 1; + } + + xmlNs *ns = pool_malloc(pool, sizeof(xmlNs)); + if(!ns) { + return 1; + } + memset(ns, 0, sizeof(xmlNs)); + ns->prefix = (const xmlChar*)pool_strdup(pool, xmlns_prefix); + ns->href = (const xmlChar*)pool_strdup(pool, xmlns_href); + if(!ns->prefix || !ns->href) { + return 1; + } + + char *value = pool_malloc(pool, len+1); + if(!value) { + return 1; + } + memcpy(value, str, len); + value[len] = '\0'; + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + return res->addproperty(res, property, 200); +} + +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, + WSBool allprop, + 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 = removefromlist; + 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); + } + } + } + + if(allprop) { + ret.creationdate = 1; + ret.getcontentlength = 1; + ret.getetag = 1; + ret.getlastmodified = 1; + ret.getresourcetype = 1; + } + + return ret; +} + +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, contentlength); + if(webdav_resource_add_dav_stringproperty(res, pool, "getcontentlength", buf, strlen(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(webdav_resource_add_dav_stringproperty(res, pool, "getlastmodified", buf, strlen(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\"", + (int)s->st_size, + (int)s->st_mtim.tv_sec); + if(webdav_resource_add_dav_stringproperty(res, pool, "getetag", buf, strlen(buf))) { + return 1; + } + } + + return 0; +}