--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,687 @@ +/* + * 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 void send_string_escaped(Writer *out, sstr_t str) { + char *begin = str.ptr; + char *end = begin; + char *escape = NULL; + int esclen; + for(size_t i=0;i<str.length;i++) { + char c = str.ptr[i]; + end = str.ptr + i; + switch(c) { + case '"': { + escape = """; + esclen = 6; + break; + } + case '&': { + escape = "&"; + esclen = 5; + break; + } + case '\'': { + escape = "'"; + esclen = 6; + break; + } + case '<': { + escape = "<"; + esclen = 4; + break; + } + case '>': { + escape = ">"; + esclen = 4; + break; + } + default: continue; + } + ptrdiff_t len = end - begin; + if(len > 0) { + writer_put(out, begin, len); + begin = end + 1; + } + writer_put(out, escape, esclen); + } + ptrdiff_t len = end - begin; + if(len > 0) { + writer_put(out, begin, len + 1); + begin = end + 1; + } +} + +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)); + send_string_escaped(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")); + + //printf("\n\n"); + //fflush(stdout); + + 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; +}