Sun, 01 May 2022 10:48:20 +0200
add WebdavNSList <-> string converting functions
/* * 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; }