src/server/webdav/multistatus.c

Tue, 31 Dec 2019 11:57:02 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 31 Dec 2019 11:57:02 +0100
branch
webdav
changeset 218
2ba512b284b9
parent 217
8ed14d76db42
child 222
5f05e56cb8e2
permissions
-rw-r--r--

add webdav_op_propfind_begin test that checks backend chaining

/*
 * 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 "../daemon/session.h"

#include "operation.h"

#include "multistatus.h"

#define MULTISTATUS_BUFFER_LENGTH 2048

Multistatus* multistatus_response(Session *sn, Request *rq) {
    Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus));
    if(!ms) {
        return NULL;
    }
    ZERO(ms, sizeof(Multistatus)); 
    ms->response.addresource = multistatus_addresource;
    ms->sn = sn;
    ms->rq = rq;
    ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8);
    if(!ms->namespaces) {
        return NULL;
    }
    if(ucx_map_cstr_put(ms->namespaces, "D", "DAV:")) {
        return NULL;
    }
    return ms;
}

static int send_xml_root(Multistatus *ms, Writer *out) {
    writer_puts(out, S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
                       "<D:multistatus"));
    
    // write the namespaces definitions
    // key is the namespace prefix
    // the map always contains the "DAV:" namespace with the prefix "D"
    UcxMapIterator i = ucx_map_iterator(ms->namespaces);
    char *href;
    UCX_MAP_FOREACH(key, href, i) {
        writer_puts(out, S(" xmlns:"));
        writer_put(out, key.data, key.len);
        writer_puts(out, S("=\""));
        writer_puts(out, sstr(href));
        writer_puts(out, S("\""));
    }
    
    writer_puts(out, S(">\n"));
    
    return out->error;
}

static int send_prop_name(
        Multistatus *ms,
        WebdavProperty *p,
        Writer *out,
        char *prefixbuf,
        int *prefixbuflen)
{
    int in_prefix_len = *prefixbuflen;
    *prefixbuflen = 0;
    
    writer_putc(out, '<');
    
    const char *prefix = NULL;
    const char *href = NULL;
    
    if(p->namespace && p->namespace->href) {
        href = (const char*)p->namespace->href;

        if(p->namespace->prefix) {
            // check if there is a namespace with this prefix already defined
            // and has the same namespace uri
            prefix = (const char*)p->namespace->prefix;
            char *nshref = ucx_map_cstr_get(
                    ms->namespaces,
                    (const char*)p->namespace->prefix);
            if(!strcmp(nshref, href)) {
                href = NULL; // we don't need a new xmlns def
            }
        } else {
            // generate new prefix
            for(int i=0;i<1024;i++) {
                int len = snprintf(prefixbuf, in_prefix_len, "x%d\0", i);
                char *test = ucx_map_cstr_get(ms->namespaces, prefixbuf);
                if(!test) {
                    prefix = prefixbuf;
                    *prefixbuflen = len;
                    break; // found an unused prefix
                }
                if(!prefix) {
                    // What? Can't find a free prefix?
                    return 1;
                }
            }
        }
    }
    
    if(prefix) {
        writer_put(out, prefix, strlen(prefix));
        writer_put(out, ":", 1);
    }
    
    // write xml element name
    writer_put(out, (const char*)p->name, strlen((const char*)p->name));
    
    if(href) {
        writer_puts(out, S(" xmlns:"));
        writer_put(out, prefix, strlen(prefix));
        writer_puts(out, S("=\""));
        writer_put(out, href, strlen(href));
        writer_putc(out, '\"');
    }
    
    if(p->lang) {
        writer_puts(out, S(" lang=\""));
        writer_puts(out, sstr(p->lang));
        writer_putc(out, '\"');
    }
    
    writer_putc(out, '>');
    
    return out->error;
}



#define MAX_XML_TREE_DEPTH 128
static int send_xml(Multistatus *ms, Writer *out, WSXmlNode *node, WSNamespace *rootns, int depth) {
    int ret = 0;
    const char *s;
    while(node) {
        switch(node->type) {
            case XML_ELEMENT_NODE: {
                writer_putc(out, '<');
                if(node->ns && node->ns->prefix) {
                    s = (const char*)node->ns->prefix;
                    writer_put(out, s, strlen(s));
                    writer_putc(out, ':');
                }
                s = (const char*)node->name;
                writer_put(out, s, strlen(s));
                
                
            }
            
        }
        node = node->next;
    }
    
    return ret;
}

static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) {
    writer_puts(out, S("  <D:response>\n"
                       "    <D:href>"));
    writer_puts(out, sstr(rp->resource.href));
    writer_puts(out, S("</href>\n"));
    
    if(rp->plist_begin) {
        writer_puts(out, S("    <D:propstat>"
                           "      <D:prop>\n"));
        WebdavPList *p = rp->plist_begin;
        char prefix[16];
        while(p) {
            WebdavProperty *prop = p->property;
            int prefixlen = 16;
            if(send_prop_name(ms, prop, out, prefix, &prefixlen)) {
                return 1;
            }
            
            // send content
            
            
            // send end tag
            writer_put(out, "<", 1);
            if(prop->namespace && prop->namespace->href) {
                const char *pre = NULL;
                if(prop->namespace->prefix) {
                    pre = (const char*)prop->namespace->prefix;
                } else if(prefixlen > 0) {
                    pre = prefix;
                }
                
                if(pre) {
                    writer_put(out, pre, strlen(pre));
                    writer_put(out, ":", 1);
                }
            }
            writer_put(out, prop->name, strlen(prop->name));
            writer_put(out, ">", 1);
            
            if(out->error) {
                return 1;
            }
            
            p = p->next;
        }
        writer_puts(out, S("      </D:prop>\n"
                           "      <D:status>HTTP/1.1 200 OK</D:status>"
                           "    </D:propstat>\n"));
    }
    
    return out->error;
}

int multistatus_send(Multistatus *ms, SYS_NETFD net) {
    char buffer[MULTISTATUS_BUFFER_LENGTH];
    Writer writer;
    Writer *out = &writer;
    writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH);
    
    // send the xml root element with namespace defs
    if(send_xml_root(ms, out)) {
        return 1;
    }
    
    // send response tags
    MSResponse *response = ms->first;
    while(response) {
        if(send_response_tag(ms, response, out)) {
            return 1;
        }
        response = response->next;
    }
    
    return 0;
}


WebdavResource * multistatus_addresource(
        WebdavResponse *response,
        const char *path)
{
    Multistatus *ms = (Multistatus*)response;
    MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse));
    if(!res) {
        return NULL;
    }
    ZERO(res, sizeof(MSResponse));
    
    res->resource.addproperty = msresponse_addproperty;
    res->resource.close = msresponse_close;
    
    res->multistatus = ms;
    res->errors = NULL;
    res->resource.isclosed = 0;
    res->closing = 0;
    
    if(ms->current) {
        if(!ms->current->resource.isclosed) {
            msresponse_close((WebdavResource*)ms->current);
        }
        ms->current->next = res;
    } else {
        ms->first = res;
    }
    ms->current = res;
    
    return (WebdavResource*)res;
}

int msresponse_addproperty(
        WebdavResource *res,
        WebdavProperty *property,
        int status)
{
    MSResponse *response = (MSResponse*)res;
    if(response->resource.isclosed) {
        log_ereport(
                LOG_WARN,
                "%s",
                "webdav: cannot add property to closed response tag");
        return 0;
    }
    
    // add namespace of this property to the namespace map
    if(property->namespace && property->namespace->prefix) {
        char *ns = ucx_map_cstr_get(
                response->multistatus->namespaces,
                (const char*)property->namespace->prefix);
        if(!ns) {
            int err = ucx_map_cstr_put(
                    response->multistatus->namespaces,
                    (const char*)property->namespace->prefix,
                    property->namespace->href);
            if(err) {
                return 1;
            }
        }
    }
    
    if(status != 200) {
        return msresponse_addproperror(response, property, status);
    }
    
    // add property to the list
    WebdavPList *listelm = pool_malloc(
            response->multistatus->sn->pool,
            sizeof(WebdavPList));
    if(!listelm) {
        return 1;
    }
    
    listelm->property = property;
    listelm->next = NULL;
    
    if(response->plist_end) {
        response->plist_end->next = listelm;
    } else {
        response->plist_begin = listelm;
    }
    response->plist_end = listelm;
    return 0;
}

int msresponse_addproperror(
        MSResponse *response,
        WebdavProperty *property,
        int statuscode)
{
    pool_handle_t *pool = response->multistatus->sn->pool;
    UcxAllocator *a = session_get_allocator(response->multistatus->sn);
    
    // MSResponse contains a list of properties for each status code
    // at first find the list for this status code
    PropertyErrorList *errlist = NULL;
    PropertyErrorList *list = response->errors;
    PropertyErrorList *last = NULL;
    while(list) {
        if(list->status == statuscode) {
            errlist = list;
            break;
        }
        last = list;
        list = list->next;
    }
    
    if(!errlist) {
        // no list available for this statuscode
        PropertyErrorList *newelm = pool_malloc(pool,
                sizeof(PropertyErrorList));
        if(!newelm) {
            return 1;
        }
        newelm->begin = NULL;
        newelm->end = NULL;
        newelm->next = NULL;
        newelm->status = statuscode;
        
        if(last) {
            last->next = newelm;
        } else {
            response->errors = newelm;
        }
        errlist = newelm;
    }
    
    // we have the list -> add the new element
    UcxList *newlistelm = ucx_list_append_a(a, errlist->end, property);
    if(!newlistelm) {
        return 1;
    }
    errlist->end = newlistelm;
    if(!errlist->begin) {
        errlist->begin = newlistelm;
    }
    return 0;
}

int msresponse_close(WebdavResource *res) {
    MSResponse *response = (MSResponse*)res;
    if(response->closing) {
        return 0; // close already in progress
    }
    
    int ret = REQ_PROCEED;
    WebdavOperation *op = response->multistatus->response.op;
    if(webdav_op_propfiond_close_resource(op, res)) {
        ret = REQ_ABORTED;
    }
    
    response->resource.isclosed = TRUE;
    response->closing = FALSE;
    return ret;
}

mercurial