--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.c Thu Oct 31 10:26:35 2019 +0100 @@ -0,0 +1,391 @@ +/* + * 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 "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->multistatus = ms; + res->errors = NULL; + res->end = 0; + + if(ms->current) { + ms->current->end = 1; + 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->end) { + 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; +}