src/server/webdav/requestparser.c

changeset 385
a1f4cb076d2f
parent 373
f78a585e1a2f
child 415
d938228c382e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/webdav/requestparser.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,496 @@
+/*
+ * 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 <string.h>
+
+#include <ucx/string.h>
+#include <ucx/utils.h>
+#include <ucx/map.h>
+
+#include "requestparser.h"
+#include "webdav.h"
+
+#define xstreq(a, b) !strcmp((const char*)a, (const char*)b)
+
+void proplist_free(pool_handle_t *pool, WebdavPList *list) {
+    while(list) {
+        WebdavPList *next = list->next;
+        pool_free(pool, list);
+        list = next;
+    }
+}
+
+WebdavProperty* prop_create(
+        pool_handle_t *pool,
+        WSNamespace *ns,
+        const char *name)
+{
+    WebdavProperty *prop = pool_malloc(pool, sizeof(WebdavProperty));
+    memset(prop, 0, sizeof(WebdavProperty));
+    prop->lang = NULL;
+    prop->name = (char*)name;
+    prop->namespace = ns;
+    return prop;
+}
+
+static int parse_prop(
+        Session *sn,
+        xmlNode *node,
+        UcxMap *propmap,
+        WebdavPList **plist_begin,
+        WebdavPList **plist_end,
+        size_t *propcount,
+        int proppatch,
+        int *error)
+{
+    xmlNode *pnode = node->children;
+    for(;pnode;pnode=pnode->next) {
+        if(pnode->type != XML_ELEMENT_NODE) {
+            continue;
+        }
+        
+        const char* ns = (const char*)pnode->ns->href;
+        const char* name = (const char*)pnode->name;
+        
+        // check for prop duplicates
+        UcxKey k = webdav_property_key((const char*)ns, (const char*)name);
+        if(!k.data) {
+            *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            return 1;
+        }
+        void *c = ucx_map_get(propmap, k);
+        if(!c) {
+            if(ucx_map_put(propmap, k, (void*)1)) {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+            
+            // no duplicate
+            // create property elment and add it to the list
+            WebdavProperty *prop = prop_create(sn->pool, pnode->ns, name);
+            if(proppatch) {
+                prop->value.node = pnode->children;
+                prop->vtype = WS_VALUE_XML_NODE;
+            }
+            if(prop) {
+                if(webdav_plist_add(sn->pool, plist_begin, plist_end, prop)) {
+                    *error = proppatch ?
+                        PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+                }
+                (*propcount)++;
+            } else {
+                *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
+            }
+        } else if(proppatch) {
+            *error = PROPPATCH_PARSER_DUPLICATE;
+        }
+        
+        free((void*)k.data);
+        if(*error) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+WebdavPropfindRequest* propfind_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPFIND_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    WSBool allprop = FALSE;
+    WSBool propname = FALSE;
+    WebdavPList *plist_begin = NULL;
+    WebdavPList *plist_end = NULL;
+    size_t propcount = 0;
+    int depth = webdav_getdepth(rq);
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is DAV:propfind
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "propfind")))
+    {
+        *error = PROPFIND_PARSER_NO_PROPFIND;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    UcxMap *propmap = ucx_map_new(32);
+    if(!propmap) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->ns->href, "DAV:") && !allprop && !propname) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "prop")) {
+                    ret = parse_prop(
+                            sn,
+                            node,
+                            propmap,
+                            &plist_begin,
+                            &plist_end,
+                            &propcount,
+                            0, // proppatch = false
+                            error);
+                } else if(xstreq(node->name, "allprop")) {
+                    allprop = TRUE;
+                } else if(xstreq(node->name, "propname")) {
+                    propname = TRUE;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // no allocated content must be freed
+    
+    if(ret) {
+        // parse_prop failed
+        // in this case, error is already set
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    if(!allprop && !propname && propcount == 0) {
+        *error = PROPFIND_PARSER_NO_PROPERTIES;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPropfindRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavPropfindRequest));
+    if(!request) {
+        *error = PROPFIND_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->properties = NULL;
+    request->propcount = 0;
+    request->depth = depth;
+    request->doc = doc;
+    if(allprop) {
+        request->allprop = TRUE;
+        request->propname = FALSE; // we cannot have allprop and propname
+    } else if(propname) {
+        request->allprop = FALSE;
+        request->propname = TRUE;
+    } else {
+        request->allprop = FALSE;
+        request->propname = FALSE;
+        request->properties = plist_begin;
+        request->propcount = propcount;
+    }
+    
+    if(!request->properties && plist_begin) {
+        proplist_free(sn->pool, plist_begin);
+    }
+    return request;
+}
+
+WebdavProppatchRequest* proppatch_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    return webdav_parse_set(
+            sn, rq, buf, buflen, "DAV:", "propertyupdate", TRUE, error);
+}
+
+static xmlNode* find_child(xmlNode *node, const char *name) {
+    xmlNode *c = node->children;
+    while(c) {
+        if(c->ns) {
+            if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, name)) {
+                return c;
+            }
+        }
+        c = c->next;
+    }
+    return NULL;
+}
+
+WebdavProppatchRequest* webdav_parse_set(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        const char *rootns,
+        const char *rootname,
+        WSBool allowremove,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, rootns)
+            && xstreq(root->name, rootname)))
+    {
+        *error = PROPPATCH_PARSER_NO_PROPERTYUPDATE;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    // ret vars
+    *error = 0;
+    
+    UcxMap *propmap = ucx_map_new(32); // map for duplicate checking
+    if(!propmap) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavPList *set_begin = NULL;
+    WebdavPList *set_end = NULL;
+    WebdavPList *remove_begin = NULL;
+    WebdavPList *remove_end = NULL;
+    size_t set_count = 0;
+    size_t remove_count = 0;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(node->ns && xstreq(node->ns->href, "DAV:")) {
+                // a propfind request can contain a prop element
+                // with specified properties or the allprop or propname
+                // element
+                if(xstreq(node->name, "set")) {
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &set_begin,
+                            &set_end,
+                            &set_count,
+                            TRUE, // proppatch = true
+                            error);
+                } else if(xstreq(node->name, "remove")) {
+                    if(!allowremove) {
+                        *error = PROPPATCH_PARSER_INVALID_REQUEST;
+                        ret = 1;
+                        break;
+                    }
+                    xmlNode *prop = find_child(node, "prop");
+                    ret = parse_prop(
+                            sn,
+                            prop,
+                            propmap,
+                            &remove_begin,
+                            &remove_end,
+                            &remove_count,
+                            TRUE, // proppatch = true
+                            error);
+                }
+                    
+            }
+        }
+        node = node->next;
+    }
+    
+    ucx_map_free(propmap); // allocated content must not be freed
+    
+    if(set_count + remove_count == 0) {
+        *error = PROPPATCH_PARSER_NO_PROPERTIES;
+        ret = 1;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavProppatchRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavProppatchRequest));
+    if(!request) {
+        *error = PROPPATCH_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    request->set = set_begin;
+    request->setcount = set_count;
+    request->remove = remove_begin;
+    request->removecount = remove_count;
+    return request;
+}
+
+WebdavLockRequest* lock_parse(
+        Session *sn,
+        Request *rq,
+        const char *buf,
+        size_t buflen,
+        int *error)
+{
+    xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0);
+    if(!doc) {
+        *error = LOCK_PARSER_INVALID_REQUEST;
+        return NULL;
+    }
+    
+    xmlNode *root = xmlDocGetRootElement(doc);
+    xmlNode *node = root->children;
+    
+    // check if the root element is correct
+    if(
+            !(root->ns
+            && xstreq(root->ns->href, "DAV:")
+            && xstreq(root->name, "lockinfo")))
+    {
+        *error = LOCK_PARSER_NO_LOCKINFO;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockScope lockscope = WEBDAV_LOCK_SCOPE_UNKNOWN;
+    WebdavLockType locktype = WEBDAV_LOCK_TYPE_UNKNOWN;
+    WSXmlNode *owner = NULL;
+    
+    int ret = 0;
+    while(node && !ret) {
+        if(
+                node->type == XML_ELEMENT_NODE
+                && node->ns
+                && xstreq(node->ns->href, "DAV:"))
+        {
+            char *name = (char*)node->name;
+            if(xstreq(name, "lockscope")) {
+                xmlNode *s = node->children;
+                while(s) {
+                    if(
+                            s->type == XML_ELEMENT_NODE
+                            && s->ns
+                            && xstreq(s->ns->href, "DAV:"))
+                    {
+                        if(xstreq(s->name, "exclusive")) {
+                            lockscope = WEBDAV_LOCK_EXCLUSIVE;
+                        } else if(xstreq(s->name, "shared")) {
+                            lockscope = WEBDAV_LOCK_SHARED;
+                        } else {
+                            // don't ignore unknown lockscope
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    s = s->next;
+                }
+            } else if(xstreq(name, "locktype")) {
+                xmlNode *t = node->children;
+                while(t) {
+                    if(
+                            t->type == XML_ELEMENT_NODE
+                            && t->ns
+                            && xstreq(t->ns->href, "DAV:"))
+                    {
+                        if(xstreq(t->name, "write")) {
+                            locktype = WEBDAV_LOCK_WRITE;
+                        } else {
+                            *error = LOCK_PARSER_UNKNOWN_ELEMENT;
+                            ret = 1;
+                            break;
+                        }
+                    }
+                    t = t->next;
+                }
+            } else if(xstreq(name, "owner")) {
+                owner = node->children;
+            }
+        }
+        node = node->next;
+    }
+    
+    if(ret) {
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    
+    WebdavLockRequest *request = pool_malloc(
+            sn->pool,
+            sizeof(WebdavLockRequest));
+    if(!request) {
+        *error = LOCK_PARSER_OOM;
+        xmlFreeDoc(doc);
+        return NULL;
+    }
+    request->sn = sn;
+    request->rq = rq;
+    request->doc = doc;
+    
+    if(locktype == WEBDAV_LOCK_TYPE_UNKNOWN) {
+        locktype = WEBDAV_LOCK_WRITE;
+    }
+    if(lockscope == WEBDAV_LOCK_SCOPE_UNKNOWN) {
+        lockscope = WEBDAV_LOCK_EXCLUSIVE;
+    }
+    request->scope = lockscope;
+    request->type = locktype;
+    request->owner = owner;
+    return request;
+}
+

mercurial