src/server/webdav/webdav.c

Sun, 29 Dec 2019 21:43:14 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 29 Dec 2019 21:43:14 +0100
branch
webdav
changeset 214
4d7ac67a1c14
parent 213
4a6be4f10d5f
child 215
68e824ba4a4f
permissions
-rw-r--r--

add tests for webdav_propfind_init and fix wrong backend call

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2019 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <ucx/buffer.h>
#include <ucx/list.h>

#include "webdav.h"

#include "search.h"
#include "versioning.h"
#include "multistatus.h"
#include "requestparser.h"

#include "../util/pblock.h"
#include "../util/util.h"
#include "../daemon/session.h"
#include "../daemon/http.h"

static UcxMap *method_handler_map;

static WebdavBackend default_backend;

static WSNamespace dav_namespace;

static WebdavProperty dav_resourcetype_empty;
static WebdavProperty dav_resourcetype_collection;
static WSXmlNode dav_resourcetype_collection_value; // TODO: change type to WSXmlData

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

int webdav_init(pblock *pb, Session *sn, Request *rq) {
    init_default_backend();
    
    method_handler_map = ucx_map_new(64);
    
    ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options);
    ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind);
    ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch);
    ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol);
    ucx_map_cstr_put(method_handler_map, "POST", webdav_post);
    ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete);
    ucx_map_cstr_put(method_handler_map, "PUT", webdav_put);
    ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy);
    ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move);
    ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock);
    ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock);
    ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report);
    ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl);
    
    ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search);
    
    ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control);
    ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout);
    ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin);
    ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout);
    ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace);
    ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update);
    ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label);
    ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge);
    
    dav_namespace.href = (xmlChar*)"DAV:";
    dav_namespace.prefix = (xmlChar*)"D";
    
    dav_resourcetype_empty.namespace = &dav_namespace;
    dav_resourcetype_empty.name = "resourcetype";
    
    dav_resourcetype_collection.namespace = &dav_namespace;
    dav_resourcetype_collection.name = "resourcetype";
    dav_resourcetype_collection.value.node = &dav_resourcetype_collection_value;
    dav_resourcetype_collection.vtype = WS_VALUE_XML_NODE;
    dav_resourcetype_collection_value.content = (xmlChar*)"<D:collection/>";
    dav_resourcetype_collection_value.type = XML_TEXT_NODE;
    
    
    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) {
    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;
        }
    }
    
    // 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;
    }
    // WebdavResponse is the public interface used by Backends
    // for adding resources to the response
    WebdavResponse *response = (WebdavResponse*)ms;
    
    WebdavBackend *dav = 
            rq->davCollection ? rq->davCollection : &default_backend;
    
    // requested uri path
    char *path = pblock_findkeyval(pb_key_path, rq->vars);
    
    // VFS settings are only taken from the first backend
    uint32_t settings = dav->settings;
    
    // list of individual WebdavPropfindRequest objects for each Backend
    UcxList *requestObjects = NULL;
    
    // Initialize all Webdav Backends
    if(webdav_propfind_init(dav, propfind, path, &requestObjects)) {
        return REQ_ABORTED;
    }
    
    // some Backends can list all children by themselves, but some
    // require the VFS for this
    WSBool usevfs = (settings & WS_PROPFIND_NO_VFS) != WS_PROPFIND_NO_VFS;
    struct stat s;
    struct stat *statptr = NULL;
    
    VFSContext *vfs = NULL;
    if(usevfs) {
        vfs = vfs_request_context(sn, rq);
        
        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_ABORTED;
    if(!webdav_propfind_do(dav, requestObjects, response, NULL, path, 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 && !propfind_children(dav, requestObjects, response, vfs, path)) {
            ret = REQ_PROCEED;
        }
    }
    
    // finish the propfind request
    // this function should cleanup all resources, therefore we execute it
    // even if a previous function failed
    if(dav->propfind_finish(propfind)) {
        ret = REQ_ABORTED;
    }
    
    return ret;
}

/*
 * Initializes Backend Chain
 * 
 * Calls propfind_init of each Backend and generates a list of custom
 * WebdavPropfindRequest objects for each backend
 */
int webdav_propfind_init(
        WebdavBackend *dav,
        WebdavPropfindRequest *propfind,
        const char *path,
        UcxList **out_req)
{   
    pool_handle_t *pool = propfind->sn->pool;
    UcxAllocator *a = session_get_allocator(propfind->sn);
    
    // list of individual WebdavPropfindRequest objects for each Backend
    UcxList *requestObjects = NULL;
    
    // new properties after init, start with clone of original plist
    WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties);
    size_t newPropCount = propfind->propcount;
    
    // Call propfind_init for each Backend
    // propfind_init can return a new property list, which
    // will be passed to the next backend

    WebdavBackend *davList = dav;
    while(davList) {
        // create WebdavPropfindRequest copy
        WebdavPropfindRequest *pReq = pool_malloc(
                pool,
                sizeof(WebdavPropfindRequest));
        memcpy(propfind, pReq, sizeof(WebdavPropfindRequest));
        // use new plist after previous init (or orig. plist in the first run)
        pReq->properties = newProp;
        pReq->propcount = newPropCount;
        
        // add new WebdavPropfindRequest object to list for later use
        requestObjects = ucx_list_append_a(a, requestObjects, pReq);
        if(!requestObjects) {
            return REQ_ABORTED; // OOM
        }
        
        // create plist copy as out-plist for init
        newProp = webdav_plist_clone(pool, newProp);
        
        // run init: this can generate a new properties list (newProp)
        //           which will be passed to the next backend
        if(davList->propfind_init(pReq, path, &newProp)) {
            return REQ_ABORTED;
        }
        
        newPropCount = webdav_plist_count(newProp);
        
        davList = davList->next;
    }
    
    *out_req = requestObjects;
    return REQ_PROCEED;
}


/*
 * Executes propfind_do for each Backend
 * The list requests must contain all WebdavPropfindRequest objects
 * of all backends
 */
int webdav_propfind_do(
        WebdavBackend *dav,
        UcxList *requests,
        WebdavResponse *response,
        VFS_DIR parent,
        const char *path,
        struct stat *s)
{
    while(dav && requests) {
        if(dav->propfind_do(requests->data, response, parent, path, s)) {
            return REQ_ABORTED;
        }
        
        dav = dav->next;
        requests = requests->next;
    }
    return REQ_PROCEED;
}

/*
 * Executes propfind_finish for each Backend
 */
int webdav_propfind_finish(WebdavBackend *dav, UcxList *requests) {
    int ret = REQ_PROCEED;
    while(dav && requests) {
        if(dav->propfind_finish(requests->data)) {
            ret = REQ_ABORTED;
        }
        
        dav = dav->next;
        requests = requests->next;
    }
    return ret;
}


/*
 * Uses the VFS to iterate over all children of the requsted resource
 * and executes propfind for each child
 */
int propfind_children(
        WebdavBackend *dav,
        UcxList *requests,
        WebdavResponse *response,
        VFSContext *vfs,
        char *path)
{
    WebdavPropfindRequest *request = requests->data;
    
    UcxAllocator *a = session_get_allocator(request->sn);
    pool_handle_t *pool = request->sn->pool;
    UcxList *stack = ucx_list_prepend_a(a, NULL, path);
    UcxList *stack_end = stack;
    if(!stack) {
        return 1;
    }
    
    // reusable buffer for full child path
    char *newpath = NULL;
    size_t newpathlen = 0;
    
    int err = 0;
    while(stack && !err) {
        char *cur_path = stack->data;
        size_t parent_len = strlen(cur_path);
        if(parent_len > WEBDAV_PATH_MAX) {
            log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
            err = 1;
            break;
        }
        if(cur_path[parent_len-1] == '/') {
            parent_len--;
        }
        size_t max_child_len = WEBDAV_PATH_MAX - parent_len;
        
        // when newpath is initialized with the parent path
        // set path_buf_init to TRUE
        WSBool path_buf_init = FALSE;
        
        VFS_DIR dir = vfs_opendir(vfs, path);
        if(!dir) {
            log_ereport(
                    LOG_FAILURE,
                    "webdav: propfind: cannot open directory %d",
                    vfs->vfs_errno);
            err = 1;
            break;
        }
        
        VFS_ENTRY f;
        while(vfs_readdir_stat(dir, &f)) {
            if(f.stat_errno != 0) {
                continue;
            }
            
            size_t child_len = strlen(f.name);
            if(child_len > max_child_len) {
                log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded");
                err = 1;
                break;
            }
            size_t childpathlen = parent_len + child_len + 1; // +1 '/'
            if(childpathlen > newpathlen) {
                // we're gonna need a bigger boa^H^H^Hbuffer
                if(newpath) {
                    pool_free(pool, newpath);
                }
                newpath = pool_malloc(pool, childpathlen + 1);
                if(!newpath) {
                    err = 1;
                    break;
                }
                newpathlen = childpathlen;
                path_buf_init = FALSE;
            }
            // create full path string for this child
            if(!path_buf_init) {
                memcpy(newpath, cur_path, parent_len);
                newpath[parent_len] = '/';
            }
            memcpy(newpath+parent_len+1, f.name, child_len);
            newpath[childpathlen] = 0;
            
            // propfind for this child
            if(webdav_propfind_do(dav, requests, response, dir, newpath, &f.stat)) {
                err = 1;
                break;
            }
            
            // depth of -1 means infinity
            if(request->depth == -1 && S_ISDIR(f.stat.st_mode)) {
                char *pathcp = pool_malloc(pool, childpathlen + 1);
                memcpy(pathcp, newpath, childpathlen + 1);
                
                // add the newpath copy to the stack
                // stack_end is always not NULL here, because we remove
                // the first stack element at the end of the loop
                UcxList *newlistelm = ucx_list_append_a(a, stack_end, pathcp);
                if(!newlistelm) {
                    err = 1;
                    break;
                }
                stack_end = newlistelm;
            }
        }
        
        vfs_closedir(dir);
        
        if(cur_path != path) {
            pool_free(pool, cur_path);
        }
        stack = ucx_list_remove_a(a, stack, stack);
    }
    
    // in case of an error, we have to free all remaining stack elements
    UCX_FOREACH(elm, stack) {
        char *data = elm->data;
        if(data != path) {
            pool_free(pool, data);
        }
    }
    
    return err;
}

int webdav_proppatch(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_mkcol(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_post(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_delete(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_put(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_copy(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_move(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_lock(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_unlock(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_report(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}

int webdav_acl(pblock *pb, Session *sn, Request *rq) {
    return REQ_ABORTED;
}


/* ------------------------ default webdav backend  ------------------------ */

int default_propfind_init(
        WebdavPropfindRequest *rq,
        const char* path,
        WebdavPList **outplist)
{
    DefaultWebdavData *data = pool_malloc(
            rq->sn->pool,
            sizeof(DefaultWebdavData));
    if(!data) {
        return 1;
    }
    rq->userdata = data;
    
    data->vfsproperties = webdav_vfs_properties(rq, TRUE, 0);
    
    return 0;
}

int default_propfind_do(
        WebdavPropfindRequest *request,
        WebdavResponse *response,
        VFS_DIR parent,
        const char *path,
        struct stat *s)
{
    DefaultWebdavData *data = request->userdata;
    
    // add a resource to the response
    // usually this will lead to a <response> ... </response> tag in the
    // multistatus response
    WebdavResource *resource = response->addresource(response, path);
    if(!resource) {
        return 1;
    }
    
    // add all requested vfs properties like getcontentlength ...
    if(webdav_add_vfs_properties(
            resource,
            request->sn->pool,
            data->vfsproperties,
            s))
    {
        return 1;
    }
    
    // all remaining properties are not available
    WebdavPList *p = request->properties;
    while(p) {
        resource->addproperty(resource, p->property, 404);
        p = p->next;
    }
    
    return 0;
}

int default_propfind_finish(WebdavPropfindRequest *rq) {
    return 0;
}


/* ------------------------------ public API ------------------------------ */

int webdav_getdepth(Request *rq) {
    char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers);
    int depth = 0;
    if(depth_str) {
        size_t dlen = strlen(depth_str);
        if(!memcmp(depth_str, "infinity", dlen)) {
            depth = -1;
        } else if(dlen == 1 && depth_str[0] == '1') {
            depth = 1;
        }
    }
    return depth;
}

WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) {
    WebdavPList *new_list = NULL;     // start of the new list
    WebdavPList *new_list_end = NULL; // end of the new list
    
    WebdavPList *elm = list;
    while(elm) {
        // copy list item
        WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList));
        if(!new_elm) {
            return NULL;
        }
        new_elm->property = elm->property; // new list contains original ptr
        new_elm->prev = NULL;
        new_elm->next = NULL;
        
        if(new_list_end) {
            new_list_end->next = elm;
            elm->prev = new_list_end;
            
            new_list_end = elm;
        } else {
            new_list = new_elm;
            new_list_end = new_elm;
        }
        
        elm = elm->next;
    }
    
    return new_list;
}

size_t webdav_plist_count(WebdavPList *list) {
    size_t count = 0;
    WebdavPList *elm = list;
    while(elm) {
        count++;
        elm = elm->next;
    }
    return count;
}

WSNamespace* webdav_dav_namespace(void) {
    return &dav_namespace;
}

WebdavProperty* webdav_dav_property(
        pool_handle_t *pool,
        const char *name)
{
    WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
    if(!property) {
        return NULL;
    }
    memset(property, 0, sizeof(WebdavProperty));
    
    property->namespace = &dav_namespace;
    property->name = name;
    return property;
}

int webdav_property_set_value(
        WebdavProperty *p,
        pool_handle_t *pool,
        char *value)
{
    WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode));
    if(!node) {
        return 1;
    }
    ZERO(node, sizeof(WSXmlNode));
    
    node->content = (xmlChar*)value;
    node->type = XML_TEXT_NODE;
    
    p->value.node = node;
    p->vtype = WS_VALUE_XML_NODE;
    return 0;
}

WebdavVFSProperties webdav_vfs_properties(
        WebdavPropfindRequest *rq,
        WSBool removefromlist,
        uint32_t flags)
{
    WebdavVFSProperties ret;
    ZERO(&ret, sizeof(WebdavVFSProperties));
    
    WSBool etag = 1;
    WSBool creationdate = 1;
    
    WebdavPList *property = rq->properties;
    WebdavPList *prev = NULL;
    while(property) {
        WebdavPList *next = property->next;
        WSNamespace *ns = property->property->namespace;
        if(ns && !strcmp((char*)ns->href, "DAV:")) {
            const char *name = property->property->name;
            WebdavPList *removethis = property;
            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 {
                removethis = NULL;
            }
            
            if(removefromlist && removethis) {
                
                if(prev) {
                    prev->next = next;
                } else {
                    rq->properties = next;
                }
            }
        }
        prev = property;
        property = next;
    }
    
    return ret;
}

static inline int w_addprop(
        WebdavResource *res,
        pool_handle_t *pool,
        const char *name,
        char *value)
{
    WebdavProperty *p = webdav_dav_property(pool, name);
    if(!p) {
        return 1;
    }
    if(webdav_property_set_value(p, pool, value)) {
        return 1;
    }
    return res->addproperty(res, p, 200);
}

int webdav_add_vfs_properties(
        WebdavResource *res,
        pool_handle_t *pool,
        WebdavVFSProperties properties,
        struct stat *s)
{
    if(properties.getresourcetype) {
        if(S_ISDIR(s->st_mode)) {
            res->addproperty(res, &dav_resourcetype_collection, 200);
        } else {
            res->addproperty(res, &dav_resourcetype_empty, 200);
        }
    }
    if(properties.getcontentlength) {
        char *buf = pool_malloc(pool, 64);
        if(!buf) {
            return 1;
        }
        uint64_t contentlength = s->st_size;
        snprintf(buf, 64, "%" PRIu64 "\0", contentlength);
        if(w_addprop(res, pool, "getcontentlength", buf)) {
            return 1;
        }
    }
    if(properties.getlastmodified) {
        char *buf = pool_malloc(pool, HTTP_DATE_LEN+1);
        if(!buf) {
            return 1;
        }
        buf[HTTP_DATE_LEN] = 0;
        
        struct tm mtms;
        struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms);
        
        if(mtm) {
            strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
            if(w_addprop(res, pool, "getlastmodified", buf)) {
                return 1;
            }
        } else {
            return 1;
        }
    }
    if(properties.creationdate) {
        // TODO
    }
    if(properties.getetag) {
        char *buf = pool_malloc(pool, 96);
        if(!buf) {
            return 1;
        }
        snprintf(buf,
            96,
            "\"%x-%x\"\0",
            (int)s->st_size,
            (int)s->st_mtim.tv_sec);
        if(w_addprop(res, pool, "getetag", buf)) {
            return 1;
        }
    }
    
    return 0;
}

mercurial