src/server/webdav/xml.c

Fri, 17 Jan 2020 22:23:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 17 Jan 2020 22:23:30 +0100
branch
webdav
changeset 231
4714468b9b7e
parent 225
e4f3e1433098
child 232
499711b2a970
permissions
-rw-r--r--

implement multistatus writer

/*
 * 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/map.h>

#include "../util/util.h"

#include "xml.h"

/* ------------------------     utils      ------------------------ */

/*
 * generates a string key for an xml namespace
 * format: prefix '\0' href
 */
static sstr_t namespace_key(UcxAllocator *a, WSNamespace *ns) {
    sstr_t key = sstrcat_a(a, 3,
            ns->prefix ? sstr((char*)ns->prefix) : S("\0"),
            S("\0"),
            sstr((char*)ns->href));
    return key;
}


/* ------------------------ wsxml_iterator ------------------------ */

typedef struct StackElm {
    WSXmlNode *node; // list of nodes
    //WSXmlNode *parent; // if not NULL, call endcb after node->next is NULL
    int endonly;
    struct StackElm *next;
} StackElm;

#define STACK_PUSH(stack, elm) if(stack) { elm->next = stack; } stack = elm;

int wsxml_iterator(
        pool_handle_t *pool,
        WSXmlNode *node,
        wsxml_func begincb,
        wsxml_func endcb,
        void *udata)
{  
    if(!node) {
        return 0;
    }
    
    StackElm *stack = pool_malloc(pool, sizeof(StackElm));
    if(!stack) {
        return 1; // OOM
    }
    stack->next = NULL;
    stack->node = node;
    stack->endonly = 0;
    //stack->parent = NULL;
    
    int ret = 0;
    int br = 0;
    while(stack) {
        StackElm *cur = stack;
        WSXmlNode *xmlnode = cur->node; // get top stack element
        stack = cur->next;              // and remove it
        cur->next = NULL;
        
        while(xmlnode && !cur->endonly) {
            // element begin callback
            if(begincb(xmlnode, udata)) {
                br = 1;
                break; // I don't like break with labels - is this wrong?
            }
            
            if(xmlnode->children) {
                // put the children on the stack
                // the next stack iteration will process the children
                StackElm *newelm = pool_malloc(pool, sizeof(StackElm));
                if(!newelm) {
                    ret = 1;
                    br = 1;
                    break;
                }
                newelm->next = NULL;
                newelm->node = xmlnode->children;
                // setting the parent will make sure endcb will be called
                // for the current xmlnode after all children are processed
                //newelm->parent = xmlnode;
                newelm->endonly = 0;
                
                // if xmlnode->next is not NULL, there are still nodes at
                // this level, therefore we have to put these also on the
                // stack
                // this way, the remaining nodes are processed after all
                // children and the end tag are processed
                if(xmlnode->next) {
                    StackElm *nextelm = pool_malloc(pool, sizeof(StackElm));
                    if(!nextelm) {
                        ret = 1;
                        br = 1;
                        break;
                    }
                    nextelm->node = xmlnode->next;
                    nextelm->next = NULL;
                    nextelm->endonly = 0;
                    STACK_PUSH(stack, nextelm);
                }
                
                // we have to put the end tag of the current element
                // on the stack to ensure endcb is called for the current
                // element, after all children are processed
                // reuse cur
                cur->node = xmlnode;
                cur->endonly = 1;
                STACK_PUSH(stack, cur);
                
                cur = NULL;
                
                // now we can put the children on the stack
                STACK_PUSH(stack, newelm);
                // break, because we don't want to process xmlnode->next now
                break;
            } else {
                // no children means, the end callback can be called directly
                // after the begin callback (no intermediate nodes)
                cur->node = NULL;
                if(endcb(xmlnode, udata)) {
                    br = 1;
                    break;
                }
            }
            
            // continue with next node at this level
            xmlnode = xmlnode->next;
        }
        if(br) {
            break; // break because of an error
        }
        
        if(cur && cur->node) {
            //xmlNode *endNode = cur->parent ? cur->parent : cur->node;
            xmlNode *endNode = cur->node;
            if(endcb(endNode, udata)) {
                break;
            }
            pool_free(pool, cur);
        }
    }
    
    // free all remaining elements
    StackElm *elm = stack;
    while(elm) {
        StackElm *next = elm->next;
        pool_free(pool, elm);
        elm = next;
    }

    return ret;
}

/* ------------------------ wsxml_get_namespaces ------------------------ */

typedef struct WSNsCollector {
    UcxAllocator *a;
    UcxMap *nsmap;
    WebdavNSList *def;
    int error;
} WSNsCollector;

static int nslist_node_begin(xmlNode *node, void *userdata) {
    WSNsCollector *col = userdata;
    // namespace required for all elements
    if(node->type == XML_ELEMENT_NODE && node->ns) {
        // we create a list of unique prefix-href namespaces by putting
        // all namespaces in a map
        sstr_t nskey = namespace_key(col->a, node->ns);
        if(!nskey.ptr) {
            col->error = 1;
            return 1;
        }
        if(ucx_map_sstr_put(col->nsmap, nskey, node->ns)) {
            col->error = 1;
            return 1;
        }
        
        // collect all namespace definitions for removing these namespaces
        // from col->nsmap later
        WSNamespace *def = node->nsDef;
        while(def) {
            WebdavNSList *newdef = col->a->malloc(
                    col->a->pool, sizeof(WebdavNSList));
            if(!newdef) {
                col->error = 1;
                return 1;
            }
            newdef->namespace = def;
            newdef->prev = NULL;
            newdef->next = NULL;
            // prepend newdef to the list
            if(col->def) {
                newdef->next = col->def;
                col->def->prev = newdef;
            }
            col->def = newdef;
            
            // continue with next namespace definition
            def = def->next;
        }
    }
    return 0;
}

static int nslist_node_end(xmlNode *node, void *userdata) {
    return 0;
}

WebdavNSList* wsxml_get_required_namespaces(
        pool_handle_t *pool,
        WSXmlNode *node,
        int *error)
{
    if(error) *error = 0;
    
    UcxAllocator a = util_pool_allocator(pool);
    UcxMap *nsmap = ucx_map_new_a(&a, 16);
    if(!nsmap) {
        if(error) *error = 1;
        return NULL;
    }
    
    WSNsCollector col;
    col.a = &a;
    col.nsmap = nsmap;
    col.def = NULL;
    
    // iterate over all xml elements
    // this will fill the hashmap with all namespaces
    // all namespace definitions are added to col.def
    WebdavNSList *list = NULL;
    WebdavNSList *end = NULL;
    if(wsxml_iterator(pool, node, nslist_node_begin, nslist_node_end, &col)) {
        if(error) *error = 1;
    } else {
        // remove all namespace definitions from the map
        // what we get is a map that contains all missing namespace definitions
        WebdavNSList *def = col.def;
        while(def) {
            sstr_t nskey = namespace_key(&a, def->namespace);
            if(!nskey.ptr) {
                if(error) *error = 1;
                break;
            }
            ucx_map_sstr_remove(nsmap, nskey);
            def = def->next;
        }
        
        // convert nsmap to a list
        UcxMapIterator i = ucx_map_iterator(nsmap);
        WSNamespace *ns;
        UCX_MAP_FOREACH(key, ns, i) {
            WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList));
            if(!newelm) {
                if(error) *error = 1;
                list = NULL;
                break;
            }
            newelm->namespace = ns;
            newelm->next = NULL;
            newelm->prev = end; // NULL or the end of list
            if(end) {
                end->next = newelm; // append new element
            } else {
                list = newelm; // start new list
            }
            end = newelm;
        }
    }
    
    ucx_map_free(nsmap);
    return list;
}

mercurial