src/server/webdav/requestparser.c

Sat, 25 Mar 2023 17:18:51 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 25 Mar 2023 17:18:51 +0100
changeset 489
921f83a8943f
parent 415
d938228c382e
child 490
d218607f5a7e
permissions
-rw-r--r--

fix PUT could potentially return a wrong status code

/*
 * 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 <cx/string.h>
#include <cx/utils.h>
#include <cx/map.h>
#include <cx/hash_map.h>

#include "requestparser.h"
#include "webdav.h"

#include "../util/pool.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,
        CxMap *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
        CxHashKey k = webdav_property_key((const char*)ns, (const char*)name);
        if(!k.data.bytes) {
            *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM;
            return 1;
        }
        void *c = cxMapGet(propmap, k);
        if(!c) {
            if(cxMapPut(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.str);
        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;
    }
    
    CxAllocator *a = pool_allocator(sn->pool);
    CxMap *propmap = cxHashMapCreate(a, 32); // value: intptr_t
    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;
    }
    
    cxMapDestroy(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;
    
    CxAllocator *a = pool_allocator(sn->pool);
    // map for duplicate checking
    // value type: intptr_t
    CxMap *propmap = cxHashMapCreate(a, 32);
    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;
    }
    
    cxMapDestroy(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