Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * 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 <cx/buffer.h> #include <cx/list.h> #include <cx/linked_list.h> #include <cx/hash_map.h> #include <cx/printf.h> #include "webdav.h" #include "search.h" #include "versioning.h" #include "multistatus.h" #include "requestparser.h" #include "operation.h" #include "xattrbackend.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 CxMap *method_handler_map; /* * webdav backend types * key: backend name (string) * value: WebdavBackend* */ static CxMap *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 WebdavBackend* default_backend_create(Session *sn, Request *rq, pblock *pb, void *initData) { WebdavBackend *dav = pool_malloc(sn->pool, sizeof(WebdavBackend)); if(!dav) { return NULL; } *dav = default_backend; return dav; } static int 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; return webdav_register_backend("default", NULL, default_backend_create); } 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 cxMapPut(webdav_type_map, cx_hash_key_str(name), webdavType); } WebdavType* webdav_get_type(cxstring dav_class) { return cxMapGet(webdav_type_map, cx_hash_key_bytes((unsigned const char *)dav_class.ptr, dav_class.length)); } 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 = cxMapGet(webdav_type_map, cx_hash_key_str(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 = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); if(!webdav_type_map) { return REQ_ABORTED; } method_handler_map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64); if(!method_handler_map) { return REQ_ABORTED; } if(init_default_backend()) { return REQ_ABORTED; } if(webdav_init_xattr_backend()) { return REQ_ABORTED; } cxMapPut(method_handler_map, cx_hash_key_str("OPTIONS"), webdav_options); cxMapPut(method_handler_map, cx_hash_key_str("PROPFIND"), webdav_propfind); cxMapPut(method_handler_map, cx_hash_key_str("PROPPATCH"), webdav_proppatch); cxMapPut(method_handler_map, cx_hash_key_str("MKCOL"), webdav_mkcol); cxMapPut(method_handler_map, cx_hash_key_str("POST"), webdav_post); cxMapPut(method_handler_map, cx_hash_key_str("DELETE"), webdav_delete); cxMapPut(method_handler_map, cx_hash_key_str("PUT"), webdav_put); cxMapPut(method_handler_map, cx_hash_key_str("COPY"), webdav_copy); cxMapPut(method_handler_map, cx_hash_key_str("MOVE"), webdav_move); cxMapPut(method_handler_map, cx_hash_key_str("LOCK"), webdav_lock); cxMapPut(method_handler_map, cx_hash_key_str("UNLOCK"), webdav_unlock); cxMapPut(method_handler_map, cx_hash_key_str("REPORT"), webdav_report); cxMapPut(method_handler_map, cx_hash_key_str("ACL"), webdav_acl); cxMapPut(method_handler_map, cx_hash_key_str("SEARCH"), webdav_search); cxMapPut(method_handler_map, cx_hash_key_str("VERSION-CONTROL"), webdav_version_control); cxMapPut(method_handler_map, cx_hash_key_str("CHECKOUT"), webdav_checkout); cxMapPut(method_handler_map, cx_hash_key_str("CHECKIN"), webdav_checkin); cxMapPut(method_handler_map, cx_hash_key_str("UNCHECKOUT"), webdav_uncheckout); cxMapPut(method_handler_map, cx_hash_key_str("MKWORKSPACE"), webdav_mkworkspace); cxMapPut(method_handler_map, cx_hash_key_str("UPDATE"), webdav_update); cxMapPut(method_handler_map, cx_hash_key_str("LABEL"), webdav_label); cxMapPut(method_handler_map, cx_hash_key_str("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)cxMapGet(method_handler_map, cx_hash_key_str(method)); if(!saf) { return REQ_NOACTION; } return saf(pb, sn, rq); } int rqbody2buffer(Session *sn, Request *rq, CxBuffer *buf) { if(!sn->inbuf) { //request body required, set http response code protocol_status(sn, rq, 400, NULL); return 1; } CxAllocator *a = pool_allocator(sn->pool); if(cxBufferInit(buf, NULL, sn->inbuf->maxsize, a, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { protocol_status(sn, rq, 500, NULL); return 1; } char in[2048]; int r; while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { if(cxBufferWrite(in, 1, r, buf) != r) { protocol_status(sn, rq, 500, NULL); cxBufferDestroy(buf); return 1; } } return 0; } int webdav_options(pblock *pb, Session *sn, Request *rq) { return REQ_ABORTED; } static const char* propfind_error2str(int error) { switch(error) { case PROPFIND_PARSER_OK: return "ok"; case PROPFIND_PARSER_NO_PROPFIND: return "invalid xml root element"; case PROPFIND_PARSER_NO_PROPERTIES: return "no properties specified"; case PROPFIND_PARSER_INVALID_REQUEST: return "invalid propfind request"; case PROPFIND_PARSER_OOM: return "OOM"; case PROPFIND_PARSER_ERROR: return "error"; } return ""; } 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; } } } CxBuffer reqbody; if(rqbody2buffer(sn, rq, &reqbody)) { return REQ_ABORTED; } int error = 0; WebdavPropfindRequest *propfind = propfind_parse( sn, rq, reqbody.space, reqbody.size, &error); cxBufferDestroy(&reqbody); if(!propfind) { log_ereport(LOG_FAILURE, "webdav-propfind: %s", propfind_error2str(error)); 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) { if(multistatus_send(ms, sn->csd)) { ret = REQ_ABORTED; } } else if(rq->status_num < 400 || rq->status_num >= 500) { log_ereport(LOG_FAILURE, "webdav-propfind: operation failed"); } // 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, WebdavPropfindRequestList **out_req) { pool_handle_t *pool = propfind->sn->pool; CxAllocator *a = pool_allocator(pool); // list of individual WebdavPropfindRequest objects for each Backend WebdavPropfindRequestList *requestObjectsBegin = NULL; WebdavPropfindRequestList *requestObjectsEnd = 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 WebdavPropfindRequestList *reqListElm = pool_malloc(pool, sizeof(WebdavPropfindRequestList)); if(!reqListElm) { return REQ_ABORTED; // OOM } reqListElm->propfind = pReq; reqListElm->next = NULL; cx_linked_list_add( (void**)&requestObjectsBegin, (void**)&requestObjectsEnd, -1, offsetof(WebdavPropfindRequestList, next), reqListElm); // 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 = requestObjectsBegin; 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 WebdavPropfindRequestList *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; } static const char* proppatch_error2str(int error) { switch(error) { case PROPPATCH_PARSER_OK: return "ok"; case PROPPATCH_PARSER_NO_PROPERTYUPDATE: return "no property update"; case PROPPATCH_PARSER_NO_PROPERTIES: return "no properties"; case PROPPATCH_PARSER_INVALID_REQUEST: return "invalid proppatch request"; case PROPPATCH_PARSER_DUPLICATE: return "proppatch property duplicate"; case PROPPATCH_PARSER_OOM: return "OOM"; case PROPPATCH_PARSER_ERROR: return "proppatch parser error"; } return "error"; } 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; } } } CxBuffer reqbody; if(rqbody2buffer(sn, rq, &reqbody)) { // most likely OOM return REQ_ABORTED; } int error = 0; WebdavProppatchRequest *proppatch = proppatch_parse( sn, rq, reqbody.space, reqbody.size, &error); cxBufferDestroy(&reqbody); if(!proppatch) { log_ereport(LOG_FAILURE, "webdav-proppatch: %s", proppatch_error2str(error)); 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)) { log_ereport(LOG_FAILURE, "webdav-proppatch: proppatch operation failed"); ret = REQ_ABORTED; } // send response if(ret == REQ_PROCEED) { if(multistatus_send(ms, sn->csd)) { log_ereport(LOG_FAILURE, "webdav-proppatch: multistatus_send failed"); } } else { protocol_status(sn, rq, 500, NULL); } // 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 if(rq->status_num <= 0) { 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; } else { log_ereport(LOG_VERBOSE, "webdav_mkcol: errno: %d", op->vfs->vfs_errno); } 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; struct DeleteFile *prev; struct DeleteFile *next; } DeleteFile; typedef struct DeleteLists { CxAllocator *a; DeleteFile *dirs_begin; DeleteFile *dirs_end; DeleteFile *files_begin; DeleteFile *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 = cxMalloc(op->a, sizeof(DeleteFile)); if(!file) { return 1; } file->path = cx_strdup_a(op->a, cx_str((char*)path)).ptr; if(!file->path) { return 1; } file->s = *s; file->next = NULL; // determine which list to use DeleteFile **begin; DeleteFile **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 cx_linked_list_add( (void**)begin, (void**)end, offsetof(DeleteFile, prev), offsetof(DeleteFile, next), file); return 0; } static int webdav_delete_collection(WebdavVFSOperation *op) { DeleteOp del; ZERO(&del, sizeof(DeleteOp)); del.a = pool_allocator(op->sn->pool); // 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; root.prev = NULL; root.next = del.dirs_begin; if(del.dirs_begin) { del.dirs_begin->prev = &root; } else { del.dirs_end = &root; } del.dirs_begin = &root; // delete files first for(DeleteFile *file=del.files_begin;file;file=file->next) { 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(DeleteFile *file=del.dirs_end;file;file=file->prev) { 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; } vfs->error_response_set = FALSE; // reset error 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; if(!s) { // stat is required for the default vfs // if s is null, the dav backend config is not right // (multiple backends configured and the primary backend doesn't // use the VFS) return 1; } // 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 ------------------------------ */ CxHashKey webdav_property_key_a(const CxAllocator *a, const char *ns, const char *name) { CxHashKey key; cxmutstr data = cx_asprintf("%s\n%s", name, ns); if(data.ptr) { key.data = data.ptr; key.len = data.length; cx_hash_murmur(&key); } else { key.data = NULL; key.len = 0; key.hash = 0; } return key; } CxHashKey webdav_property_key(const char *ns, const char *name) { return webdav_property_key_a(cxDefaultAllocator, ns, name); } /* ------------------------------ 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_mtime, &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_mtime); if(webdav_resource_add_dav_stringproperty(res, pool, "getetag", buf, strlen(buf))) { return 1; } } return 0; }