src/server/webdav/webdav.c

Thu, 15 Aug 2024 22:38:03 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 15 Aug 2024 22:38:03 +0200
changeset 549
b26bec196a2e
parent 490
d218607f5a7e
permissions
-rw-r--r--

fix http_stream_parse_chunk_header: check if the chunk starts with a digit

/*
 * 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;
}

mercurial