Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * 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 <cx/string.h> #include <cx/hash_map.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 = cxHashMapCreate(pool_allocator(sn->pool), CX_STORE_POINTERS, 8); ms->proppatch = FALSE; if(!ms->namespaces) { return NULL; } if(cxMapPut(ms->namespaces, cx_hash_key_str("D"), webdav_dav_namespace())) { return NULL; } return ms; } static int send_xml_root(Multistatus *ms, Writer *out) { writer_put_lit(out, "<?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" CxIterator i = cxMapIterator(ms->namespaces); cx_foreach(CxMapEntry*, entry, i) { WSNamespace *ns = entry->value; writer_put_lit(out, " xmlns:"); writer_put (out, entry->key->data, entry->key->len); writer_put_lit(out, "=\""); writer_put_str(out, (char*)ns->href); writer_put_lit(out, "\""); } writer_put_lit(out, ">\n"); return out->error; } static void send_nsdef(WSNamespace *ns, Writer *out) { writer_put_lit(out, " xmlns:"); writer_put_str(out, (char*)ns->prefix); writer_put_lit(out, "=\""); writer_put_str(out, (char*)ns->href); writer_putc (out, '\"'); } // html escape /* static void send_string_escaped(Writer *out, cxmutstr 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 const char hex_ch[] = "0123456789ABCDEF"; static void send_string_escaped(Writer *out, cxmutstr str) { char *begin = str.ptr; char *end = begin; char escape[4]; escape[0] = '%'; for(size_t i=0;i<str.length;i++) { unsigned char c = (unsigned char)str.ptr[i]; end = str.ptr + i; // check if the character must be escaped if( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '/' && c <= '9') || (strchr("_.\\-~", c) != NULL)) { continue; } // convert char to hex number, escape[0] always contains '%' escape[1] = hex_ch[(c >> 4) & 0x0F]; escape[2] = hex_ch[c & 0x0F]; // write previous unescaped chars, if available ptrdiff_t len = end - begin; if(len > 0) { writer_put(out, begin, len); } // write escaped char writer_put(out, escape, 3); // begin next chunk begin = end + 1; } ptrdiff_t len = str.ptr + str.length - begin; if(len > 0) { writer_put(out, begin, len); begin = end + 1; } } static int send_property( Multistatus *ms, WebdavProperty *property, WebdavNSList *nsdef, WSBool writeContent, Writer *out) { // write: "<prefix:name" writer_putc (out, '<'); writer_put_str(out, (char*)property->namespace->prefix); writer_putc (out, ':'); writer_put_str(out, (char*)property->name); // 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_put_lit(out, " xml:lang=\""); writer_put_str(out, (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_put_lit(out, "</"); writer_put_str(out, (char*)property->namespace->prefix); writer_putc (out, ':'); writer_put_str(out, (char*)property->name); writer_putc (out, '>'); } else { writer_put_lit(out, "/>"); } return out->error; } static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) { writer_put_lit(out, " <D:response>\n" " <D:href>"); send_string_escaped(out, cx_mutstr(rp->resource.href)); writer_put_lit(out, "</D:href>\n"); WSBool writeContent = ms->proppatch ? FALSE : TRUE; if(rp->plist_begin) { writer_put_lit(out, " <D:propstat>\n" " <D:prop>\n"); // send properties PropertyOkList *p = rp->plist_begin; while(p) { writer_put_lit(out, " "); if(send_property(ms, p->property, p->nsdef, writeContent, out)) { return out->error; } writer_put_lit(out, "\n"); p = p->next; } writer_put_lit(out, " </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_put_lit(out, " <D:propstat>\n" " <D:prop>\n"); WebdavPList *errprop = error->begin; while(errprop) { writer_put_lit(out, " "); 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_put_lit(out, " </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_put_lit(out, "Server Error"); } writer_put_lit(out, "</D:status>\n" " </D:propstat>\n"); error = error->next; } // end response tag writer_put_lit(out, " </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); if(protocol_start_response(ms->sn, ms->rq)) { return 1; } 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_put_lit(out, "</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 = cxHashMapCreate(pool_allocator(ms->sn->pool), CX_STORE_POINTERS, 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; } /* * should only be called from msresponse_addproperty * * Adds a property to the error list with the specified statuscode */ static int msresponse_addproperror( MSResponse *response, WebdavProperty *property, int statuscode) { pool_handle_t *pool = response->multistatus->sn->pool; 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; } static CxHashKey ms_property_key( CxAllocator *a, const xmlChar *href, const char *property_name) { cxmutstr key_data = cx_strcat_a(a, 3, cx_str((const char*)href), (cxstring){ "\0", 1 }, cx_str(property_name)); return cx_hash_key_bytes((unsigned char*)key_data.ptr, key_data.length); } 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 CxAllocator *a = pool_allocator(sn->pool); CxHashKey key = ms_property_key(a, property->namespace->href, property->name); if(cxMapGet(response->properties, key)) { cxFree(a, (void*)key.data); return 0; } if(cxMapPut(response->properties, key, property)) { return 1; // OOM } cxFree(a, (void*)key.data); // 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 = cxMapGet( response->multistatus->namespaces, cx_hash_key_str((const char*)property->namespace->prefix)); if(!ns) { // prefix is not in use -> we can add the namespace to the ns map int err = cxMapPut( response->multistatus->namespaces, cx_hash_key_str((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_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 CxAllocator *a = pool_allocator(ms->sn->pool); WebdavPList *pl = ms->response.op->reqprops; while(pl) { CxHashKey key = ms_property_key(a, pl->property->namespace->href, pl->property->name); if(!cxMapGet(response->properties, key)) { // property was not added to this response if(ms->proppatch) { if(msresponse_addproperty(res, pl->property, 424)) { ret = REQ_ABORTED; break; } } else { if(msresponse_addproperty(res, 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_addproperty(res, 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 cxMapDestroy(response->properties); response->resource.isclosed = TRUE; return ret; }