src/server/webdav/multistatus.c

Mon, 25 Apr 2022 18:33:03 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 25 Apr 2022 18:33:03 +0200
branch
webdav
changeset 310
523fe96baeca
parent 309
fc021bd576d4
child 349
7bf652914e9b
permissions
-rw-r--r--

remove createtestdb from build

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2020 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 "../daemon/protocol.h"
#include "../util/platform.h"

#include <ucx/string.h>

#include "multistatus.h"

#include "operation.h"
#include "xml.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);
    ms->proppatch = FALSE;
    if(!ms->namespaces) {
        return NULL;
    }
    if(ucx_map_cstr_put(ms->namespaces, "D", webdav_dav_namespace())) {
        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);
    WSNamespace *ns;
    UCX_MAP_FOREACH(key, ns, i) {
        writer_puts(out, S(" xmlns:"));
        writer_put(out, key.data, key.len);
        writer_puts(out, S("=\""));
        writer_puts(out, sstr((char*)ns->href));
        writer_puts(out, S("\""));
    }
    
    writer_puts(out, S(">\n"));
    
    return out->error;
}

static void send_nsdef(WSNamespace *ns, Writer *out) {
    writer_puts(out, S(" xmlns:"));
    writer_puts(out, sstr((char*)ns->prefix));
    writer_puts(out, S("=\""));
    writer_puts(out, sstr((char*)ns->href));
    writer_putc(out, '\"');
}

static int send_property(
        Multistatus *ms,
        WebdavProperty *property,
        WebdavNSList *nsdef,
        WSBool writeContent,
        Writer *out)
{
    // write: "<prefix:name"
    writer_putc(out, '<');
    writer_puts(out, sstr((char*)property->namespace->prefix));
    writer_putc(out, ':');
    writer_puts(out, sstr((char*)property->name));
    
    // check if the namespace is already defined
    WSBool need_nsdef = TRUE;
    WSNamespace *ns = ucx_map_cstr_get(
            ms->namespaces,
            (char*)property->namespace->prefix);
    if(ns && !strcmp(
            (const char*)ns->href,
            (const char*)property->namespace->href))
    {
        need_nsdef = FALSE; // prefix and href are the same, no need for nsdef
    }
    
    // send definition for the element's namespace
    if(need_nsdef) {
        send_nsdef(property->namespace, out);
    }
    
    // send additional namespace definitions required for the value
    WebdavNSList *def = nsdef;
    while(def) {
        send_nsdef(def->namespace, out);
        def = def->next;
    }
    
    // send xml lang attribute
    if(property->lang) {
        writer_puts(out, S(" xml:lang=\""));
        writer_puts(out, sstr((char*)property->lang));
        writer_putc(out, '\"');
    }
    
    // end property tag and write content
    if(writeContent) {
        writer_putc(out, '>');
        
        // content
        switch(property->vtype) {
            case WS_VALUE_NO_TYPE: break;
            case WS_VALUE_XML_NODE: {
                wsxml_write_nodes_without_nsdef(
                        ms->sn->pool,
                        out,
                        property->value.node);
                break;
            }
            case WS_VALUE_XML_DATA: {
                // only write data, data->namespaces is already handled
                writer_put(
                        out,
                        property->value.data->data,
                        property->value.data->length);
                break;
            }
            case WS_VALUE_TEXT: {
                // asume the text is already escaped
                writer_put(
                        out,
                        property->value.text.str,
                        property->value.text.length);
                break;
            }
        }
        
        // end tag
        writer_puts(out, S("</"));
        writer_puts(out, sstr((char*)property->namespace->prefix));
        writer_putc(out, ':');
        writer_puts(out, sstr((char*)property->name));
        writer_putc(out, '>');
    } else {
        writer_puts(out, S("/>"));
    }
    
    return out->error;
}

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("</D:href>\n"));
    
    WSBool writeContent = ms->proppatch ? FALSE : TRUE;
    
    if(rp->plist_begin) {
        writer_puts(out, S("  <D:propstat>\n"
                           "   <D:prop>\n"));
        // send properties
        PropertyOkList *p = rp->plist_begin;
        while(p) {
            writer_puts(out, S("    "));
            if(send_property(ms, p->property, p->nsdef, writeContent, out)) {
                return out->error;
            }
            writer_puts(out, S("\n"));
            p = p->next;
        }
        
        writer_puts(out, S("   </D:prop>\n"
                           "   <D:status>HTTP/1.1 200 OK</D:status>\n"
                           "  </D:propstat>\n"));
    }
    
    // send error properties
    PropertyErrorList *error = rp->errors;
    while(error) {
        writer_puts(out, S("  <D:propstat>\n"
                           "   <D:prop>\n"));
        
        WebdavPList *errprop = error->begin;
        while(errprop) {
            writer_puts(out, S("    "));
            if(send_property(ms, errprop->property, NULL, FALSE, out)) {
                return out->error;
            }
            writer_putc(out, '\n');
            errprop = errprop->next;
        }
        
        char statuscode[8];
        int sclen = snprintf(statuscode, 8, "%d ", error->status);
        if(sclen > 4) {
            statuscode[0] = '5';
            statuscode[1] = '0';
            statuscode[2] = '0';
            statuscode[3] = ' ';
            sclen = 4;
        }
        writer_puts(out, S("   </D:prop>\n"
                           "   <D:status>HTTP/1.1 "));
        writer_put(out, statuscode, sclen);
        const char *status_msg = protocol_status_message(error->status);
        if(status_msg) {
            writer_put(out, status_msg, strlen(status_msg));
        } else {
            writer_puts(out, S("Server Error"));
        }
        writer_puts(out, S("</D:status>\n"
                           "  </D:propstat>\n"));
        
        
        error = error->next;
    }
    
    // end response tag
    writer_puts(out, S(" </D:response>\n"));
    
    return out->error;
}

int multistatus_send(Multistatus *ms, SYS_NETFD net) { 
    // make sure every resource is closed
    if(ms->current && !ms->current->resource.isclosed) {
        if(msresponse_close((WebdavResource*)ms->current)) {
            return 1;
        }
    }
    
    // start http response
    protocol_status(ms->sn, ms->rq, 207, NULL);
    protocol_start_response(ms->sn, ms->rq);
    
    char buffer[MULTISTATUS_BUFFER_LENGTH];
    // create a writer, that flushes the buffer when it is filled
    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;
    }
    
    // end multistatus
    writer_puts(out, S("</D:multistatus>\n"));
    
    writer_flush(out);
    
    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));
    
    // set href
    res->resource.href = pool_strdup(ms->sn->pool, path);
    if(!res->resource.href) {
        return NULL;
    }
    
    res->resource.err = 0;
    
    // add resource funcs
    res->resource.addproperty = msresponse_addproperty;
    res->resource.close = msresponse_close;
    
    res->properties = ucx_map_new_a(session_get_allocator(ms->sn), 32);
    if(!res->properties) {
        return NULL;
    }
    
    res->multistatus = ms;
    res->errors = NULL;
    res->resource.isclosed = 0;
    res->closing = 0;
    
    // add new resource to the resource list
    if(ms->current) {
        // before adding a new resource, the current resource must be closed
        if(!ms->current->resource.isclosed) {
            msresponse_close((WebdavResource*)ms->current);
        }
        ms->current->next = res;
    } else {
        ms->first = res;
    }
    ms->current = res;
    
    return (WebdavResource*)res;
}

static int oklist_add(
        pool_handle_t *pool,
        PropertyOkList **begin,
        PropertyOkList **end,
        WebdavProperty *property,
        WebdavNSList *nsdef)
{
    PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList));
    if(!newelm) {
        return 1;
    }
    newelm->property = property;
    newelm->nsdef = nsdef;
    newelm->next = NULL;
    if(*end) {
        (*end)->next = newelm;
    } else {
        *begin = newelm;
    }
    *end = newelm;
    return 0;
}

int msresponse_addproperty(
        WebdavResource *res,
        WebdavProperty *property,
        int status)
{
    MSResponse *response = (MSResponse*)res;
    Session *sn = response->multistatus->sn;
    if(response->resource.isclosed) {
        log_ereport(
                LOG_WARN,
                "%s",
                "webdav: cannot add property to closed response tag");
        return 0;
    }
    
    // some WebdavProperty checks to make sure nothing explodes  
    if(!property->namespace || !property->namespace->href) {
        // error: namespace is required
        log_ereport(
                LOG_FAILURE,
                "%s",
                "webdav: property '%s' has no namespace",
                property->name);
        return 1;
    }
    
    // check if the property was already added to the resource
    UcxAllocator *a = session_get_allocator(sn);
    sstr_t key = sstrcat_a(
            a,
            3,
            sstr((char*)property->namespace->href),
            S("\0"),
            sstr((char*)property->name));
    if(ucx_map_sstr_get(response->properties, key)) {
        a->free(a->pool, key.ptr);
        return 0;
    }
    if(ucx_map_sstr_put(response->properties, key, property)) {
        return 1; // OOM
    }
    a->free(a->pool, key.ptr);
    
    // list of namespace definitions for this property
    WebdavNSList *nsdef_begin = NULL;
    WebdavNSList *nsdef_end = NULL;
    
    // add namespace of this property to the namespace map
    // the namespace map will be used for global namespace definitions
    if(property->namespace->prefix) {
        WSNamespace *ns = ucx_map_cstr_get(
                response->multistatus->namespaces,
                (const char*)property->namespace->prefix);
        if(!ns) {
            // prefix is not in use -> we can add the namespace to the ns map
            int err = ucx_map_cstr_put(
                    response->multistatus->namespaces,
                    (const char*)property->namespace->prefix,
                    property->namespace);
            if(err) {
                return 1; // OOM
            }
        } else if(
                strcmp((const char*)property->namespace->href,
                (const char*)ns->href))
        {
            // global namespace != local namespace
            // therefore we need a namespace definition in this element
            
            // ns-prefix != property-prefix -> add ns to nsdef
            if(webdav_nslist_add(
                    sn->pool,
                    &nsdef_begin,
                    &nsdef_end,
                    property->namespace))
            {
                return 1; // OOM
            }
        }
    }
    
    if(response->multistatus->proppatch && response->errors) {
        // in a proppatch request all operations must succeed
        // if we have an error, the property update status code must be
        // 424 Failed Dependency
        status = 424;
    }
    
    // error properties will be added to a separate list
    if(status != 200) { 
        return msresponse_addproperror(response, property, status);
    }
    
    // add all namespaces used by this property to the nsdef list
    WebdavNSList *nslist = NULL;
    if(property->vtype == WS_VALUE_XML_NODE) {
        // iterate over xml tree and collect all namespaces
        int err = 0;
        nslist = wsxml_get_required_namespaces(
                response->multistatus->sn->pool,
                property->value.node,
                &err);
        if(err) {
            return 1; // OOM
        }
    } else if(property->vtype == WS_VALUE_XML_DATA) {
        // xml data contains a list of all used namespaces
        nslist = property->value.data->namespaces;
    } // other value types don't contain xml namespaces
    
    while(nslist) {
        // only add the namespace to the definitions list, if it isn't a
        // property namespace, because the prop ns is already added
        // to the element's def list or global definitions list
        if(strcmp(
                (const char*)nslist->namespace->prefix,
                (const char*)property->namespace->prefix))
        {
            // ns-prefix != property-prefix -> add ns to nsdef
            if(webdav_nslist_add(
                    sn->pool,
                    &nsdef_begin,
                    &nsdef_end,
                    nslist->namespace))
            {
                return 1; // OOM
            }
        }
        nslist = nslist->next;
    }
    
    // add property to the list
    if(oklist_add(
            sn->pool,
            &response->plist_begin,
            &response->plist_end,
            property,
            nsdef_begin))
    {
        return 1;
    }
    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);
    
    response->resource.err++;
      
    // 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
    if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) {
        return 1;
    }
    return 0;
}

int msresponse_close(WebdavResource *res) {
    MSResponse *response = (MSResponse*)res;
    if(response->closing) {
        return 0; // close already in progress
    }
    response->closing = TRUE;
    Multistatus *ms = response->multistatus;
    
    int ret = REQ_PROCEED;
    
    // PROPFIND:
    // response_close will execute propfind_do of all remaining backends
    // after that we will have all available properties
    WebdavOperation *op = ms->response.op;
    if(op->response_close(op, res)) {
        ret = REQ_ABORTED;
    }
    
    // add missing properties with status code 404
    UcxAllocator *a = session_get_allocator(ms->sn);
    WebdavPList *pl = ms->response.op->reqprops;
    while(pl) {
        sstr_t key = sstrcat_a(
            a,
            3,
            sstr((char*)pl->property->namespace->href),
            S("\0"),
            sstr((char*)pl->property->name));
        if(!ucx_map_sstr_get(response->properties, key)) {
            // property was not added to this response
            if(ms->proppatch) {
                if(msresponse_addproperror(response, pl->property, 424)) {
                    ret = REQ_ABORTED;
                    break;
                }
            } else {
                if(msresponse_addproperror(response, pl->property, 404)) {
                    ret = REQ_ABORTED;
                    break;
                }
            }
        }
        
        pl = pl->next;
    }
    
    if(ms->proppatch && response->errors) {
        // a proppatch response must succeed entirely
        // if we have a single error prop, move all props with status 200
        // to the error list
        PropertyOkList *elm = response->plist_begin;
        PropertyOkList *nextelm;
        while(elm) {
            if(msresponse_addproperror(response, elm->property, 424)) {
                return 1;
            }
            nextelm = elm->next;
            pool_free(response->multistatus->sn->pool, elm);
            elm = nextelm;
        }
        response->plist_begin = NULL;
        response->plist_end = NULL;
    }
    
    // we don't need the properties anymore
    ucx_map_free(response->properties);
    
    response->resource.isclosed = TRUE;
    return ret;
}

mercurial