merge

Mon, 01 Jan 2018 19:54:37 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 01 Jan 2018 19:54:37 +0100
changeset 362
9a75b6df3307
parent 361
b6f2462ee055 (diff)
parent 360
125b9c976f39 (current diff)
child 363
e9ed8e130ccf

merge

--- a/dav/Makefile	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/Makefile	Mon Jan 01 19:54:37 2018 +0100
@@ -44,6 +44,8 @@
 SYNC_SRC += db.c
 SYNC_SRC += error.c
 SYNC_SRC += assistant.c
+SYNC_SRC += libxattr.c
+SYNC_SRC += tags.c
 
 
 DAV_OBJ = $(DAV_SRC:%.c=../build/tool/%$(OBJ_EXT))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dav/libxattr.c	Mon Jan 01 19:54:37 2018 +0100
@@ -0,0 +1,444 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libxattr.h"
+
+#include <errno.h>
+#include <sys/types.h>
+
+#include <string.h>
+
+#define LIST_BUF_LEN 1024
+#define LIST_ARRAY_LEN 8
+#define ATTR_BUF_LEN 1024
+
+#define ARRAY_ADD(array, pos, len, obj) if(pos >= len) { \
+        len *= 2; \
+        array = realloc(array, len * sizeof(char*)); \
+    } \
+    array[pos] = obj; \
+    pos++;
+
+
+#ifdef __linux__
+#define XATTR_SUPPORTED
+#include <sys/xattr.h>
+
+static char ** parse_xattrlist(char *buf, ssize_t length, ssize_t *nelm) {
+    size_t arraylen = LIST_ARRAY_LEN;
+    size_t arraypos = 0;
+    char **array = malloc(LIST_ARRAY_LEN * sizeof(char*));
+    
+    char *begin = buf;
+    char *name = NULL;
+    for(int i=0;i<length;i++) {
+        if(!name && buf[i] == '.') {
+            int nslen = (buf+i-begin);
+            //printf("%.*s\n", nslen, begin);
+            name = buf + i + 1;
+        }
+        if(buf[i] == '\0') {
+            char *attrname = strdup(name);
+            ARRAY_ADD(array, arraypos, arraylen, attrname);
+            begin = buf + i + 1;
+            name = 0;
+        }
+    }
+    
+    if(arraypos == 0) {
+        free(array);
+        array = NULL;
+    }
+    
+    *nelm = arraypos;
+    return array;
+}
+
+char ** xattr_list(const char *path, ssize_t *nelm) {
+    char *list = malloc(LIST_BUF_LEN);
+    ssize_t len = listxattr(path, list, LIST_BUF_LEN);
+    if(len == -1) {
+        switch(errno) {
+            case ERANGE: {
+                // buffer too, get size of attribute list
+                ssize_t newlen = listxattr(path, NULL, 0);
+                if(newlen > 0) {
+                    // second try
+                    list = realloc(list, newlen);
+                    len = listxattr(path, list, newlen);
+                    if(len != -1) {
+                        // this time it worked
+                        break;
+                    }
+                }
+            }
+            default: {
+                free(list);
+                *nelm = -1;
+                return NULL;
+            }
+        }
+    }
+    
+    char **ret = parse_xattrlist(list, len, nelm);
+    free(list);
+    return ret;
+}
+
+static char* name2nsname(const char *name) {
+    // add the 'user' namespace to the name
+    size_t namelen = strlen(name);
+    char *attrname = malloc(8 + namelen);
+    memcpy(attrname, "user.", 5);
+    memcpy(attrname+5, name, namelen + 1);
+    return attrname;
+}
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len) {
+    char *attrname = name2nsname(attr);
+    
+    char *buf = malloc(ATTR_BUF_LEN);
+    ssize_t vlen = getxattr(path, attrname, buf, ATTR_BUF_LEN);
+    if(vlen < 0) {
+        switch(errno) {
+            case ERANGE: {
+                ssize_t attrlen = getxattr(path, attrname, NULL, 0);
+                if(attrlen > 0) {
+                    free(buf);
+                    buf = malloc(attrlen);
+                    vlen = getxattr(path, attrname, buf, attrlen);
+                    if(vlen > 0) {
+                        break;
+                    }
+                }
+            }
+            default: {
+                *len = -1;
+                free(buf);
+                free(attrname);
+                return NULL;
+            }
+        }
+    }
+    
+    free(attrname);
+    *len = vlen;
+    return buf;
+}
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len) {
+    char *attrname = name2nsname(name);
+    int ret = setxattr(path, attrname, value, len, 0);
+    free(attrname);
+    return ret;
+}
+
+#endif /* Linux */
+
+#ifdef __APPLE__
+#define XATTR_SUPPORTED
+#include <sys/xattr.h>
+
+static char ** parse_xattrlist(char *buf, ssize_t length, ssize_t *nelm) {
+    size_t arraylen = LIST_ARRAY_LEN;
+    size_t arraypos = 0;
+    char **array = malloc(LIST_ARRAY_LEN * sizeof(char*));
+    
+    char *name = buf;
+    for(int i=0;i<length;i++) {
+        if(buf[i] == '\0') {
+            char *attrname = strdup(name);
+            ARRAY_ADD(array, arraypos, arraylen, attrname);
+            name = buf + i + 1;
+        }
+    }
+    
+    if(arraypos == 0) {
+        free(array);
+        array = NULL;
+    }
+    
+    *nelm = arraypos;
+    return array;
+}
+
+char ** xattr_list(const char *path, ssize_t *nelm) {
+    char *list = malloc(LIST_BUF_LEN);
+    ssize_t len = listxattr(path, list, LIST_BUF_LEN, 0);
+    if(len == -1) {
+        switch(errno) {
+            case ERANGE: {
+                // buffer too, get size of attribute list
+                ssize_t newlen = listxattr(path, NULL, 0, 0);
+                if(newlen > 0) {
+                    // second try
+                    list = realloc(list, newlen);
+                    len = listxattr(path, list, newlen, 0);
+                    if(len != -1) {
+                        // this time it worked
+                        break;
+                    }
+                }
+            }
+            default: {
+                free(list);
+                *nelm = -1;
+                return NULL;
+            }
+        }
+    }
+    
+    char **ret = parse_xattrlist(list, len, nelm);
+    free(list);
+    return ret;
+}
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len) { 
+    // get attribute length
+    ssize_t attrlen = getxattr(path, attr, NULL, 0, 0, 0);
+    if(attrlen < 0) {
+        *len = -1;
+        return NULL;
+    }
+    
+    char *buf = malloc(attrlen);
+    ssize_t vlen = getxattr(path, attr, buf, attrlen, 0, 0);
+    if(vlen < 0) {
+        *len = -1;
+        free(buf);
+        return NULL;
+    }
+    
+    *len = vlen;
+    return buf;
+}
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len) {
+    int ret = setxattr(path, name, value, len, 0, 0);
+    return ret;
+}
+
+#endif /* Apple */
+
+#ifdef __sun
+#define XATTR_SUPPORTED
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+static int open_attrfile(const char *path, const char *attr, int oflag) {
+    int file = open(path, O_RDONLY);
+    if(file == -1) {
+        return -1;
+    }
+    
+    int attrfile = openat(file, attr, oflag, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+    close(file);
+    return attrfile;
+}
+
+char ** xattr_list(const char *path, ssize_t *nelm) {
+    *nelm = -1;
+    
+    int attrdir = open_attrfile(path, ".", O_RDONLY|O_XATTR);
+    if(attrdir == -1) {
+        return NULL;
+    }
+    
+    DIR *dir = fdopendir(attrdir);
+    if(!dir) {
+        close(attrdir);
+        return NULL;
+    }
+    
+    size_t arraylen = LIST_ARRAY_LEN;
+    size_t arraypos = 0;
+    char **array = malloc(LIST_ARRAY_LEN * sizeof(char*));
+    
+    struct dirent *ent;
+    while((ent = readdir(dir)) != NULL) {
+        if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") || !strcmp(ent->d_name, "SUNWattr_ro") || !strcmp(ent->d_name, "SUNWattr_rw")) {
+            continue;
+        }
+        char *name = strdup(ent->d_name);
+        ARRAY_ADD(array, arraypos, arraylen, name);
+    }
+    closedir(dir);
+    
+    *nelm = arraypos;
+    return array;
+}
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len) {
+    *len = -1;
+    
+    int attrfile = open_attrfile(path, attr, O_RDONLY|O_XATTR);
+    if(attrfile == -1) {
+        return NULL;
+    }
+    
+    struct stat s;
+    if(fstat(attrfile, &s)) {
+        close(attrfile);
+        return NULL;
+    }
+    
+    size_t bufsize = (size_t)s.st_size;
+    char *buf = malloc(bufsize);
+    
+    char *b = buf;
+    size_t cur = 0;
+    while(cur < bufsize) {
+        ssize_t r = read(attrfile, buf + cur, bufsize - cur);
+        if(r <= 0) {
+            break;
+        }
+        cur += r;
+    }
+    
+    close(attrfile);
+    if(cur != bufsize) {
+        free(buf);
+        return NULL;
+    }
+    
+    *len = (ssize_t)bufsize;
+    return buf;    
+}
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len) {
+    int attrfile = open_attrfile(path, name, O_CREAT|O_WRONLY|O_XATTR);
+    if(attrfile == -1) {
+        return -1;
+    }
+    
+    const char *p = value;
+    size_t remaining = len;
+    while(remaining > 0) {
+        ssize_t w = write(attrfile, p, remaining);
+        if(w <= 0) {
+            break;
+        }
+        p += w;
+        remaining -= w;
+    }
+    
+    close(attrfile);
+    
+    return remaining > 0 ? -1 : 0;
+}
+
+#endif /* Sun */
+
+
+#ifdef __FreeBSD__
+#define XATTR_SUPPORTED
+
+#include <sys/types.h>
+#include <sys/extattr.h>
+
+static char ** parse_xattrlist(char *buf, ssize_t length, ssize_t *nelm) {
+    size_t arraylen = LIST_ARRAY_LEN;
+    size_t arraypos = 0;
+    char **array = malloc(LIST_ARRAY_LEN * sizeof(char*));
+    
+    char *name = buf;
+    for(int i=0;i<length;i++) {
+        char namelen = buf[i];
+        char *name = buf + i + 1;
+        char *attrname = malloc(namelen + 1);
+        memcpy(attrname, name, namelen);
+        attrname[namelen] = 0;
+        ARRAY_ADD(array, arraypos, arraylen, attrname);
+        i += namelen;
+    }
+    
+    if(arraypos == 0) {
+        free(array);
+        array = NULL;
+    }
+    
+    *nelm = arraypos;
+    return array;
+}
+
+char ** xattr_list(const char *path, ssize_t *nelm) {
+    *nelm = -1;
+    ssize_t lslen = extattr_list_file(path, EXTATTR_NAMESPACE_USER, NULL, 0);
+    if(lslen <= 0) {
+        if(lslen == 0) {
+            *nelm = 0;
+        }
+        return NULL;
+    }
+    
+    char *list = malloc(lslen);
+    ssize_t len = extattr_list_file(path, EXTATTR_NAMESPACE_USER, list, lslen);
+    if(len == -1) {
+        free(list);
+        return NULL;
+    }
+    
+    char **ret = parse_xattrlist(list, len, nelm);
+    free(list);
+    return ret;
+}
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len) { 
+    // get attribute length
+    ssize_t attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, attr, NULL, 0);
+    if(attrlen < 0) {
+        *len = -1;
+        return NULL;
+    }
+    
+    char *buf = malloc(attrlen);
+    ssize_t vlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, attr, buf, attrlen);
+    if(vlen < 0) {
+        *len = -1;
+        free(buf);
+        return NULL;
+    }
+    
+    *len = vlen;
+    return buf;
+}
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len) {
+    int ret = extattr_set_file(path, EXTATTR_NAMESPACE_USER, name, value, len);
+    return ret;
+}
+
+#endif /* FreeBSD */
+
+
+#ifndef XATTR_SUPPORTED
+
+char ** xattr_list(const char *path, ssize_t *nelm) {
+    *nelm = -1;
+    return NULL;
+}
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len) {
+    *len = -1;
+    return NULL;
+}
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len) {
+    return -1;
+}
+
+#endif /* unsupported platform */
+
+void xattr_free_list(char **attrnames, ssize_t nelm) {
+    if(attrnames) {
+        for(int i=0;i<nelm;i++) {
+            free(attrnames[i]);
+        }
+        free(attrnames);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dav/libxattr.h	Mon Jan 01 19:54:37 2018 +0100
@@ -0,0 +1,52 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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.
+ */
+
+#ifndef LIBXATTR_H
+#define LIBXATTR_H
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+char ** xattr_list(const char *path, ssize_t *nelm);
+
+char * xattr_get(const char *path, const char *attr, ssize_t *len);
+
+int xattr_set(const char *path, const char *name, const void *value, size_t len);
+
+void xattr_free_list(char **attrnames, ssize_t nelm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBXATTR_H */
+
--- a/dav/main.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/main.c	Mon Jan 01 19:54:37 2018 +0100
@@ -41,6 +41,7 @@
 #include <libidav/utils.h>
 #include <libidav/crypto.h>
 #include <libidav/session.h>
+#include <libidav/xml.h>
 #include "config.h"
 #include "error.h"
 #include "assistant.h"
@@ -61,6 +62,7 @@
 //define DO_THE_TEST
 #include <libidav/davqlparser.h>
 #include <libidav/davqlexec.h>
+#include "tags.h"
 void test() {
 
 }
@@ -1488,9 +1490,9 @@
     char *value = a->argc > 2 ? a->argv[2] : stdin2str();
     
     if(namespace) {
-        dav_set_property_ns(res, namespace, property, value);
+        dav_set_string_property_ns(res, namespace, property, value);
     } else {
-        dav_set_property(res, property, value);
+        dav_set_string_property(res, property, value);
     }
     
     int ret = 0;
@@ -1789,44 +1791,6 @@
     return space;
 }
 
-static void printnode(FILE *out, UcxMap *nsmap, DavXmlNode *node) {
-    while(node) {
-        if(node->type == DAV_XML_ELEMENT) {
-            char *prefix = ucx_map_cstr_get(nsmap, node->namespace);
-            char *freethis = NULL;
-            char *tagend = node->children ? ">" : " />";
-            if(!prefix) {
-                sstr_t newpre = ucx_sprintf("x%d", (int)nsmap->count);
-                ucx_map_cstr_put(nsmap, node->namespace, newpre.ptr);
-                freethis = newpre.ptr;
-                prefix = newpre.ptr;
-                fprintf(
-                        out,
-                        "<%s:%s xmlns:%s=\"%s\"%s",
-                        prefix,
-                        node->name,
-                        prefix,
-                        node->namespace,
-                        tagend);
-            } else {
-                fprintf(out, "<%s:%s%s", prefix, node->name, tagend);
-            }
-            
-            if(node->children) {
-                printnode(out, nsmap, node->children);
-                fprintf(out, "</%s:%s>", prefix, node->name);
-            }
-            if(freethis) {
-                free(freethis);
-            }
-        } else if(node->type == DAV_XML_TEXT) {
-            fwrite(node->content, 1, node->contentlength, out);
-        }
-        
-        node = node->next;
-    }
-}
-
 void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) {
     UcxMap *nsmap = ucx_map_new(16);
     
@@ -1834,9 +1798,14 @@
     fprintf(out, "%s", "<?xml version=\"1.0\"?>\n");
     fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns);
     
-    printnode(out, nsmap, content);
+    dav_print_node(out, (write_func)fwrite, nsmap, content);
     
     fprintf(out, "</x0:%s>\n", root);
+    
+    // cleanup namespace map
+    ucx_map_cstr_remove(nsmap, rootns);
+    ucx_map_free_content(nsmap, free);
+    ucx_map_free(nsmap);
 }
 
 
--- a/dav/scfg.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/scfg.c	Mon Jan 01 19:54:37 2018 +0100
@@ -125,6 +125,72 @@
     return 0;
 }
 
+
+static char* get_attr_content(xmlNode *node) {
+    // TODO: remove code duplication (util_xml_get_text) and config.h
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+static TagFormat str2tagformat(const char *str) {
+    if(!strcmp(str, "text")) {
+        return TAG_FORMAT_TEXT;
+    } else if(!strcmp(str, "csv")) {
+        return TAG_FORMAT_CSV;
+    } else if(!strcmp(str, "xml")) {
+        return TAG_FORMAT_XML;
+    }
+    return TAG_FORMAT_UNKNOWN;
+}
+
+static TagConfig* parse_tagconfig(xmlNode *node) {
+    TagConfig conf;
+    conf.store = TAG_STORE_XATTR;
+    conf.local_format = TAG_FORMAT_TEXT;
+    conf.server_format = TAG_FORMAT_XML;
+    xmlNode *c = node->children;
+    
+    // TODO: error handling
+    while(c) {
+        if(node->type == XML_ELEMENT_NODE) {
+            char *value = util_xml_get_text(c);
+            if(xstreq(c->name, "local-store")) {  
+                if(!value) {
+                    return NULL;
+                } else if(xstreq(value, "xattr")) {
+                    conf.store = TAG_STORE_XATTR;
+                } else {
+                    return NULL;
+                }
+                
+                xmlAttr *attr = c->properties;
+                while(attr) {
+                    if(attr->type == XML_ATTRIBUTE_NODE) {
+                        const char *value = get_attr_content(attr->children);
+                        if(!value) {
+                            break;
+                        }
+                        if(xstreq(attr->name, "format")) {
+                            conf.local_format = str2tagformat(value);
+                        }
+                    }
+                    attr = attr->next;
+                }
+            }
+        }
+        c = c->next;
+    }
+    
+    TagConfig *tagconfig = malloc(sizeof(TagConfig));
+    *tagconfig = conf;
+    return tagconfig;
+}
+
 static int scfg_load_directory(xmlNode *node) {
     char *name = NULL;
     char *path = NULL;
@@ -132,6 +198,7 @@
     char *collection = NULL;
     char *repository = NULL;
     char *database = NULL;
+    TagConfig *tagconfig = NULL;
     UcxList *include = NULL;
     UcxList *exclude = NULL;
     int max_retry = 0;
@@ -172,6 +239,8 @@
                 }
             } else if(xstreq(node->name, "database")) {
                 database = value;
+            } else if(xstreq(node->name, "tags")) {
+                tagconfig = parse_tagconfig(node);
             } else if(xstreq(node->name, "max-retry")) {
                 int64_t i;
                 if(util_strtoint(value, &i) && i >= 0) {
@@ -247,6 +316,7 @@
     dir->collection = collection ? strdup(collection) : NULL;
     dir->repository = strdup(repository);
     dir->database = strdup(database);
+    dir->tagconfig = tagconfig;
     dir->max_retry = max_retry;
     dir->allow_cmd = allow_cmd;
     dir->backuppull = backuppull;
--- a/dav/scfg.h	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/scfg.h	Mon Jan 01 19:54:37 2018 +0100
@@ -42,6 +42,8 @@
 #define SYNC_CMD_PUSH    2
 #define SYNC_CMD_ARCHIVE 4
     
+typedef struct TagConfig TagConfig;
+    
 typedef struct SyncDirectory {
     char *name;
     char *path;
@@ -49,6 +51,7 @@
     char *collection;
     char *repository;
     char *database;
+    TagConfig *tagconfig;
     UcxList *include;
     UcxList *exclude;
     int max_retry;
@@ -59,6 +62,27 @@
     bool lockpush;
 } SyncDirectory;
 
+
+enum TagFormat {
+    TAG_FORMAT_TEXT = 0,
+    TAG_FORMAT_CSV,
+    TAG_FORMAT_XML,
+    TAG_FORMAT_UNKNOWN
+};
+typedef enum TagFormat TagFormat;
+
+enum TagStore {
+    TAG_STORE_XATTR = 0,
+    TAG_STORE_UNKNOWN
+};
+typedef enum TagStore TagStore;
+
+struct TagConfig {
+    TagStore store;
+    TagFormat local_format;
+    TagFormat server_format;
+};
+
 int load_sync_config();
 
 UcxMapIterator scfg_directory_iterator();
--- a/dav/sync.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/sync.c	Mon Jan 01 19:54:37 2018 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
@@ -49,7 +49,8 @@
 #include "db.h"
 #include "error.h"
 #include "assistant.h"
-
+#include "libxattr.h"
+#include "tags.h"
 #include "sync.h"
 #include "libidav/session.h"
 
@@ -344,7 +345,7 @@
     }
     
     int ret = 0;
-    DavResource *ls = dav_query(sn, "select D:getetag,idav:status from / with depth = infinity");
+    DavResource *ls = dav_query(sn, "select D:getetag,idav:status,idav:tags from / with depth = infinity");
     if(!ls) {
         print_resource_error(sn, "/");
         if(locked) {
@@ -584,6 +585,8 @@
                 local->last_modified = 0;
                 ucx_map_cstr_put(db->resources, local->path, local);
             }
+            
+            sync_store_tags(dir, local_path, local, res);
         }
     } else {
         if(!tmp_path) {
@@ -604,6 +607,8 @@
         }
         fclose(out);
         
+        sync_store_tags(dir, tmp_path, local, res);
+        
         if(ret == 0) {
             (*counter)++;
             
@@ -1234,7 +1239,7 @@
 
 int sync_set_status(DavResource *res, char *status) {
     DavResource *resource = dav_resource_new(res->session, res->path);
-    dav_set_property(resource, "idav:status", status);
+    dav_set_string_property(resource, "idav:status", status);
     int ret = dav_store(resource);
     dav_resource_free(resource);
     return ret;
@@ -1248,6 +1253,81 @@
     return ret;
 }
 
+int sync_store_tags(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res) {
+    if(!dir->tagconfig) {
+        return 0;
+    }
+    
+    UcxList *tags = NULL;
+    if(dir->tagconfig) {
+        DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_NS, "tags");
+        if(tagsprop) {
+            tags = parse_dav_xml_taglist(tagsprop);
+        }
+    }
+    
+    if(!tags) {
+        return 0;
+    }
+    
+    int ret = 0;
+    if(dir->tagconfig->store == TAG_STORE_XATTR) {
+        UcxBuffer *data = NULL;
+        switch(dir->tagconfig->local_format) {
+            default: break;
+            case TAG_FORMAT_TEXT: {
+                data = create_text_taglist(tags);
+                break;
+            }
+            case TAG_FORMAT_CSV: {
+                data = create_csv_taglist(tags);
+                break;
+            }
+        }
+        
+        if(data) {
+            ret = xattr_set(path, "tags", data->space, data->pos);
+            ucx_buffer_free(data);
+        } else {
+            ret = -1;
+        }
+    }
+    
+    // TODO: free stuff
+    
+    return ret;
+}
+
+UcxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res) {
+    UcxList *tags = NULL;
+    
+    if(!dir->tagconfig) {
+        return NULL;
+    }
+    if(dir->tagconfig->store == TAG_STORE_XATTR) {
+        ssize_t tag_length = 0;
+        char *local_path = util_concat_path(dir->path, res->path);
+        char* tag_data = xattr_get(local_path, "tags", &tag_length);
+        free(local_path);
+        
+        if(tag_length > 0) {
+            switch(dir->tagconfig->local_format) {
+                default: break;
+                case TAG_FORMAT_TEXT: {
+                    tags = parse_text_taglist(tag_data, tag_length);
+                    break;
+                }
+                case TAG_FORMAT_CSV: {
+                    tags = parse_csv_taglist(tag_data, tag_length);
+                    break;
+                }
+            }
+        }
+    }
+    
+    return tags;
+}
+
 int sync_put_resource(
         SyncDirectory *dir,
         DavResource *res,
@@ -1273,6 +1353,14 @@
     
     dav_set_content(res, in, (dav_read_func)fread);
     
+    if(dir->tagconfig) {
+        UcxList *tags = sync_get_file_tags(dir, local);
+        DavXmlNode *prop = create_xml_taglist(tags);
+        if(prop) {
+            dav_set_property_ns(res, DAV_NS, "tags", prop);
+        }
+    }
+    
     int ret = -1;
     int created = 0;
     for(int i=0;i<=dir->max_retry;i++) {
--- a/dav/sync.h	Thu Dec 28 15:30:23 2017 +0100
+++ b/dav/sync.h	Mon Jan 01 19:54:37 2018 +0100
@@ -83,6 +83,8 @@
 
 int sync_set_status(DavResource *res, char *status);
 int sync_remove_status(DavResource *res);
+UcxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res);
+int sync_store_tags(SyncDirectory *dir, const char *path, LocalResource *local, DavResource *res);
 int sync_put_resource(
         SyncDirectory *dir,
         DavResource *res,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dav/tags.c	Mon Jan 01 19:54:37 2018 +0100
@@ -0,0 +1,177 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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 <ucx/string.h>
+#include <ucx/utils.h>
+
+#include "libxattr.h"
+
+#include "tags.h"
+
+UcxList* parse_text_taglist(const char *buf, size_t length) {
+    UcxList *tags = NULL;
+    
+    int line_start = 0;
+    for(int i=0;i<length;i++) {
+        if(buf[i] == '\n' || i == length-1) {
+            sstr_t line = sstrtrim(sstrn((char*)buf + line_start, i - line_start));
+            if(line.length > 0) {
+                DavTag *tag = calloc(1, sizeof(DavTag));
+                tag->name = sstrdup(line).ptr;
+                tag->color = NULL;
+                tags = ucx_list_append(tags, tag);
+            }
+            line_start = i+1;
+        }
+    }
+    
+    return tags;
+}
+
+UcxBuffer* create_text_taglist(UcxList *tags) {
+    if(!tags) {
+        return NULL;
+    }
+    
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    UCX_FOREACH(elm, tags) {
+        DavTag *tag = elm->data;
+        ucx_bprintf(buf, "%s\n", tag->name);
+    }
+    return buf;
+}
+
+
+UcxList* parse_csv_taglist(const char *buf, size_t length) {
+    UcxList *taglist = NULL;
+    
+    sstr_t str = sstrn((char*)buf, length);
+    ssize_t count = 0;
+    sstr_t *tags = sstrsplit(str, S(","), &count);
+    for(int i=0;i<count;i++) {
+        DavTag *tag = malloc(sizeof(DavTag));
+        tag->name = sstrdup(sstrtrim(tags[i])).ptr;
+        tag->color = NULL;
+        taglist = ucx_list_append(taglist, tag);
+        free(tags[i].ptr);
+    }
+    if(tags) {
+        free(tags);
+    }
+    return taglist;
+}
+
+UcxBuffer* create_csv_taglist(UcxList *tags) {
+    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    int insertsep = 0;
+    UCX_FOREACH(elm, tags) {
+        DavTag *tag = elm->data;
+        if(insertsep) {
+            ucx_buffer_putc(buf, ',');
+        }
+        ucx_buffer_puts(buf, tag->name);
+        insertsep = 1;
+    }
+    return buf;
+}
+
+
+static DavTag* parse_xml_dav_tag(DavXmlNode *node) {
+    char *name = NULL;
+    
+    DavXmlNode *c = node->children;
+    while(c) {
+        if(c->type == DAV_XML_ELEMENT) {
+            char *value = dav_xml_getstring(c->children);
+            if(value) {
+                if(!strcmp(c->namespace, DAV_NS)) {
+                    if(!strcmp(c->name, "name")) {
+                        char *value = dav_xml_getstring(c->children);
+                        if(value) {
+                            name = value;
+                        }
+                    }
+                    // TODO: color, ...
+                }
+            }
+        }
+        c = c->next;
+    }
+    
+    DavTag *tag = NULL;
+    if(name) {
+        tag = malloc(sizeof(DavTag));
+        tag->name = strdup(name);
+        tag->color = NULL;
+    }
+    return tag;
+} 
+
+UcxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) {
+    UcxList *tags = NULL;
+    
+    DavXmlNode *node = taglistnode;
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            if(!strcmp(node->namespace, DAV_NS) && !strcmp(node->name, "tag")) {
+                DavTag *tag = parse_xml_dav_tag(node);
+                if(tag) {
+                    tags = ucx_list_append(tags, tag);
+                }
+            }
+        } 
+        node = node->next;
+    }
+    
+    return tags;
+}
+
+DavXmlNode* create_xml_taglist(UcxList *tags) {
+    DavXmlNode *tag1 = NULL;
+    DavXmlNode *lasttag = NULL;
+    UCX_FOREACH(elm, tags) {
+        DavTag *tag = elm->data;
+        
+        DavXmlNode *tagelm = dav_xml_createnode(DAV_NS, "tag");
+        DavXmlNode *tagname = dav_xml_createnode_with_text(DAV_NS, "name", tag->name);
+        // TODO: color
+        tagelm->children = tagname;
+        
+        if(lasttag) {
+            lasttag->next = tagelm;
+            tagelm->prev = lasttag;
+        } else {
+            tag1 = tagelm;
+        }
+        lasttag = tagelm;
+    }
+    return tag1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dav/tags.h	Mon Jan 01 19:54:37 2018 +0100
@@ -0,0 +1,62 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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.
+ */
+
+#ifndef TAGS_H
+#define TAGS_H
+
+#include <ucx/string.h>
+#include <ucx/buffer.h>
+#include <ucx/list.h>
+
+#include <libidav/webdav.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavTag {
+    char *name;
+    char *color;
+} DavTag;
+    
+UcxList* parse_text_taglist(const char *buf, size_t length);
+UcxBuffer* create_text_taglist(UcxList *tags);
+
+UcxList* parse_csv_taglist(const char *buf, size_t length);
+UcxBuffer* create_csv_taglist(UcxList *tags);
+
+UcxList* parse_dav_xml_taglist(DavXmlNode *taglistnode);
+DavXmlNode* create_xml_taglist(UcxList *tags);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TAGS_H */
+
--- a/libidav/davqlexec.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/davqlexec.c	Mon Jan 01 19:54:37 2018 +0100
@@ -279,7 +279,7 @@
                             field_result.length));
                 }
             } else if(field_result.type == 2) {
-                // TODO:
+                node = dav_copy_node(field_result.data.node);
             } else {
                 // unknown type
                 // TODO: error
@@ -1072,10 +1072,17 @@
             }
             case DAVQL_CMD_PROP_IDENTIFIER: {
                 //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name);
-                char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
-                obj.type = 1;
-                obj.length = value ? strlen(value) : 0;
-                obj.data.string = value;
+                //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+                if(dav_xml_isstring(value)) {
+                    obj.type = 1;
+                    obj.length = (uint32_t)value->contentlength;
+                    obj.data.string = value->content;
+                } else {
+                    obj.type = 2;
+                    obj.length = 0;
+                    obj.data.node = value;
+                }
                 DAVQL_PUSH(obj);
                 break;
             }
--- a/libidav/methods.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/methods.c	Mon Jan 01 19:54:37 2018 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
@@ -817,7 +817,7 @@
             if(content->type == DAV_XML_TEXT) {
                 ucx_buffer_write(content->content, 1, content->contentlength, buf);
             } else {
-                // TODO: implement
+                dav_print_node(buf, (write_func)ucx_buffer_write, namespaces, content);
             }
             
             // end tag
--- a/libidav/resource.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/resource.c	Mon Jan 01 19:54:37 2018 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
@@ -475,14 +475,14 @@
     return property;
 }
 
-void dav_set_property(DavResource *res, char *name, char *value) {
+void dav_set_string_property(DavResource *res, char *name, char *value) {
     char *pns;
     char *pname;
     dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
-    dav_set_property_ns(res, pns, pname, value);
+    dav_set_string_property_ns(res, pns, pname, value);
 }
 
-void dav_set_property_ns(DavResource *res, char *ns, char *name, char *value) {
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
     UcxAllocator *a = res->session->mp->allocator;
     DavResourceData *data = res->data;
     
@@ -502,6 +502,33 @@
     data->set = ucx_list_append_a(a, data->set, property);
 }
 
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value) {
+    char *pns;
+    char *pname;
+    dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+    dav_set_property_ns(res, pns, pname, value);
+}
+
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+    UcxAllocator *a = res->session->mp->allocator;
+    DavResourceData *data = res->data;
+    
+    DavProperty *property = dav_session_malloc(
+            res->session,
+            sizeof(DavProperty));
+    property->name = dav_session_strdup(res->session, name);
+    property->value = value; // TODO: copy node?
+    
+    DavNamespace *namespace = dav_session_malloc(
+            res->session,
+            sizeof(DavNamespace));
+    namespace->prefix = NULL;
+    namespace->name = dav_session_strdup(res->session, ns);
+    property->ns = namespace;
+    
+    data->set = ucx_list_append_a(a, data->set, property);
+}
+
 void dav_remove_property(DavResource *res, char *name) {
     char *pns;
     char *pname;
--- a/libidav/webdav.h	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/webdav.h	Mon Jan 01 19:54:37 2018 +0100
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2016 Olaf Wintermann. All rights reserved.
+ * Copyright 2018 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:
@@ -288,8 +288,10 @@
 DavXmlNode* dav_get_property_ns(DavResource *res, char *ns, char *name);
 char* dav_get_string_property(DavResource *res, char *name);
 char* dav_get_string_property_ns(DavResource *res, char *ns, char *name);
-void dav_set_property(DavResource *res, char *name, char *value);
-void dav_set_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_set_string_property(DavResource *res, char *name, char *value);
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value);
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
 void dav_remove_property(DavResource *res, char *name);
 void dav_remove_property_ns(DavResource *res, char *ns, char *name);
 
@@ -307,10 +309,18 @@
 // private
 int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf);
 
+/* ------------------------ xml functions ------------------------ */
 char* dav_xml_getstring(DavXmlNode *node);
 DavBool dav_xml_isstring(DavXmlNode *node);
 DavXmlNode* dav_text_node(DavSession *sn, char *text);
 
+DavXmlNode* dav_copy_node(DavXmlNode *node);
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name);
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text);
+DavXmlNode* dav_xml_createtextnode(const char *text);
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/libidav/xml.c	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/xml.c	Mon Jan 01 19:54:37 2018 +0100
@@ -30,6 +30,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <ucx/utils.h>
+
 #include "xml.h"
 
 static DavXmlNodeType convert_type(xmlElementType type) {
@@ -135,6 +137,40 @@
     }
 }
 
+void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode *node) {
+    while(node) {
+        if(node->type == DAV_XML_ELEMENT) {
+            char *prefix = ucx_map_cstr_get(nsmap, node->namespace);
+            char *tagend = node->children ? ">" : " />";
+            if(!prefix) {
+                sstr_t newpre = ucx_sprintf("x%d", (int)nsmap->count);
+                ucx_map_cstr_put(nsmap, node->namespace, newpre.ptr);
+                prefix = newpre.ptr;
+                ucx_fprintf(
+                        stream,
+                        writef,
+                        "<%s:%s xmlns:%s=\"%s\"%s",
+                        prefix,
+                        node->name,
+                        prefix,
+                        node->namespace,
+                        tagend);
+            } else {
+                ucx_fprintf(stream, writef, "<%s:%s%s", prefix, node->name, tagend);
+            }
+            
+            if(node->children) {
+                dav_print_node(stream, writef, nsmap, node->children);
+                ucx_fprintf(stream, writef, "</%s:%s>", prefix, node->name);
+            }
+        } else if(node->type == DAV_XML_TEXT) {
+            writef(node->content, 1, node->contentlength, stream);
+        }
+        
+        node = node->next;
+    }
+}
+
 /* ------------------------- public API ------------------------- */
 
 char* dav_xml_getstring(DavXmlNode *node) {
@@ -163,3 +199,79 @@
     return newxn;
 }
 
+
+
+
+DavXmlNode* dav_copy_node(DavXmlNode *node) {
+    DavXmlNode *ret = NULL;
+    DavXmlNode *prev = NULL;
+    while(node) {
+        DavXmlNode *copy = calloc(1, sizeof(DavXmlNode));
+        copy->type = node->type;
+        if(node->type == DAV_XML_ELEMENT) {
+            copy->namespace = strdup(node->namespace);
+            copy->name = strdup(node->name);
+            copy->children = dav_copy_node(node->children);
+        } else {
+            copy->contentlength = node->contentlength;
+            copy->content = malloc(node->contentlength+1);
+            memcpy(copy->content, node->content, node->contentlength);
+            copy->content[copy->contentlength] = 0;
+        }
+        if(!ret) {
+            ret = copy;
+        }
+        if(prev) {
+            prev->next = copy;
+            copy->prev = prev;
+        }
+        prev = copy;
+        node = node->next;
+    }
+    return ret;
+}
+
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    return node;
+}
+
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_ELEMENT;
+    node->namespace = strdup(ns);
+    node->name = strdup(name);
+    
+    DavXmlNode *textnode = dav_xml_createtextnode(text);
+    node->children = textnode;
+    
+    return node;
+}
+
+DavXmlNode* dav_xml_createtextnode(const char *text) {
+    DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+    node->type = DAV_XML_TEXT;
+    sstr_t content = sstrdup(sstr((char*)text));
+    node->content = content.ptr;
+    node->contentlength = content.length;
+    return node;
+}
+
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child) {
+    DavXmlNode *last_child = NULL;
+    DavXmlNode *c = node->children;
+    while(c) {
+        last_child = c;
+        c = c->next;
+    }
+    if(last_child) {
+        last_child->next = child;
+        child->prev = last_child;
+    } else {
+        node->children = child;
+    }
+}
--- a/libidav/xml.h	Thu Dec 28 15:30:23 2017 +0100
+++ b/libidav/xml.h	Mon Jan 01 19:54:37 2018 +0100
@@ -38,6 +38,8 @@
 
 void dav_print_xml(DavXmlNode *node);
 
+void dav_print_node(void *stream, write_func writef, UcxMap *nsmap, DavXmlNode *node);
+
 
 #ifdef __cplusplus
 }

mercurial