implement proppatch of the xattr webdav backend

Sat, 18 Mar 2023 19:33:06 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 18 Mar 2023 19:33:06 +0100
changeset 481
31affbf33911
parent 480
9f69e4b8b695
child 482
b02da778362e

implement proppatch of the xattr webdav backend

src/server/webdav/webdav.c file | annotate | diff | comparison | revisions
src/server/webdav/webdav.h file | annotate | diff | comparison | revisions
src/server/webdav/xattrbackend.c file | annotate | diff | comparison | revisions
src/server/webdav/xattrbackend.h file | annotate | diff | comparison | revisions
--- a/src/server/webdav/webdav.c	Sat Mar 18 15:52:35 2023 +0100
+++ b/src/server/webdav/webdav.c	Sat Mar 18 19:33:06 2023 +0100
@@ -940,15 +940,24 @@
 
 /* ------------------------------ Utils ------------------------------ */
 
-CxHashKey webdav_property_key(const char *ns, const char *name) {
+CxHashKey webdav_property_key_a(CxAllocator *a, const char *ns, const char *name) {
     CxHashKey key;
     cxmutstr data = cx_asprintf("%s\n%s", name, ns);
-    key.data.str = data.ptr;
-    key.len = data.length;
-    cx_hash_murmur(&key);
+    if(data.ptr) {
+        key.data.str = data.ptr;
+        key.len = data.length;
+        cx_hash_murmur(&key);
+    } else {
+        key.data.str = NULL;
+        key.len = 0;
+        key.hash = 0;
+    }
     return key;
 }
 
+CxHashKey webdav_property_key(const char *ns, const char *name) {
+    return webdav_property_key_a(cxDefaultAllocator, ns, name);
+}
 
 /* ------------------------------ public API ------------------------------ */
 
--- a/src/server/webdav/webdav.h	Sat Mar 18 15:52:35 2023 +0100
+++ b/src/server/webdav/webdav.h	Sat Mar 18 19:33:06 2023 +0100
@@ -132,6 +132,8 @@
 
 CxHashKey webdav_property_key(const char *ns, const char *name);
 
+CxHashKey webdav_property_key_a(CxAllocator *a, const char *ns, const char *name);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/src/server/webdav/xattrbackend.c	Sat Mar 18 15:52:35 2023 +0100
+++ b/src/server/webdav/xattrbackend.c	Sat Mar 18 19:33:06 2023 +0100
@@ -29,8 +29,20 @@
 
 #include "xattrbackend.h"
 
+#include "webdav.h"
+
 #include "../util/util.h"
 #include "../util/libxattr.h"
+#include "../util/pblock.h"
+
+#include <inttypes.h>
+
+#include <cx/hash_map.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <libxml/tree.h>
+
 
 
 static WebdavBackend webdav_xattr_backend = {
@@ -43,7 +55,7 @@
     NULL, // opt_mkcol_finish
     NULL, // opt_delete
     NULL, // opt_delete_finish
-    WS_WEBDAV_PROPFIND_USE_VFS | WS_WEBDAV_PROPPATCH_USE_VFS, // settings
+    0,    // settings
     NULL, // instance
     NULL  // next
 };
@@ -163,6 +175,10 @@
             repo->xattr_name,
             &xattr_data_len);
     
+    if(xattr_data) {
+        
+    }
+    
     
     return 0;
 }
@@ -178,7 +194,101 @@
             WebdavPList **setInOut,
             WebdavPList **removeInOut)
 {
-    return 0;
+    Session *sn = request->sn;
+    Request *rq = request->rq;
+    CxAllocator *a = pool_allocator(sn->pool);
+    
+    WebdavXAttrBackend *xdav = request->dav->instance;
+    WebdavXAttrRepository *repo = xdav->repo;
+    
+    XAttrProppatch *xprop = pool_malloc(sn->pool, sizeof(XAttrProppatch));
+    if(!xprop) {
+        return 1;
+    }
+    request->userdata = xprop;
+    
+    char *path = pblock_findkeyval(pb_key_path, rq->vars);
+    
+    // get existing property data
+    ssize_t xattr_data_len = 0;
+    char *xattr_data = xattr_get_alloc(
+            sn->pool,
+            (libxattr_malloc_func)pool_malloc,
+            (libxattr_free_func)pool_free,
+            path,
+            repo->xattr_name,
+            &xattr_data_len);
+    
+    CxMap *pmap;
+    if(xattr_data) {
+        pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path);
+    } else {
+        // empty map
+        pmap = cxHashMapCreate(a, request->setcount + 8);
+    }
+    if(!pmap) {
+        return 1;
+    }
+    
+    // remove properties
+    WebdavPListIterator i = webdav_plist_iterator(removeInOut);
+    WebdavPList *cur;
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WebdavProperty *prop = cur->property;
+        if(!prop->namespace || !prop->namespace->prefix) {
+            // not sure if this check is required
+            log_ereport(LOG_WARN, "webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name);
+            continue;
+        }
+        
+        CxHashKey key = webdav_property_key_a(
+                a,
+                (const char*)prop->namespace->href,
+                (const char*)prop->name);
+        if(!key.data.str) {
+            cxMapDestroy(pmap);
+            return 1;
+        }
+        void *rmprop = cxMapRemove(pmap, key);
+        cxFree(a, key.data.str);
+        
+        // TODO: free rmprop
+        
+        if(rmprop) {
+            webdav_plist_iterator_remove_current(&i);
+        }
+    }
+    
+    i = webdav_plist_iterator(setInOut);
+    while(webdav_plist_iterator_next(&i, &cur)) {
+        WebdavProperty *prop = cur->property;
+        if(!prop->namespace || !prop->namespace->prefix) {
+            // not sure if this check is required
+            log_ereport(LOG_WARN, "webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name);
+            continue;
+        }
+        
+        if(webdav_xattr_put_prop(pmap, prop)) {
+            cxMapDestroy(pmap);
+            return 1;
+        }
+        
+        webdav_plist_iterator_remove_current(&i);
+    }
+    
+    //printf("\n\n%.*s\n\n", (int)buf.size, buf.space);
+    //fflush(stdout);
+    
+    int ret = 0;
+    cxmutstr new_data = webdav_xattr_serialze_map(a, pmap);
+    if(new_data.ptr) {
+        xattr_set(path, repo->xattr_name, new_data.ptr, new_data.length);
+    } else {
+        cxMapDestroy(pmap);
+        ret = 1;
+    }
+    
+    return ret;
 }
 
 int webdav_xattr_proppatch_finish(
@@ -189,3 +299,307 @@
 {
     return 0;
 }
+
+
+/* ----------------------- properties xattr data ----------------------- */
+
+static int get_next_line(cxstring data, size_t *pos, cxstring *line) {
+    size_t p = *pos;
+    cxstring str = cx_strsubs(data, p);
+    size_t i;
+    int skip = 0;
+    for(i=0;i<str.length;i++) {
+        if(str.ptr[i] == '\n') {
+            skip = 1;
+            break;
+        }
+    }
+    p += i;
+    *pos = p + skip;
+    *line = cx_strsubsl(str, 0, i);
+    return i > 0 ? TRUE : FALSE;
+}
+
+static int webdav_xattr_parse_elm(cxstring line, cxstring *name, cxstring *prefix, cxstring *xmlns, cxstring *lang) {
+    cxstring s_xmlns = CX_STR("xmlns:");
+    
+    // check if line starts with 'xmlns:'
+    if(!cx_strprefix(line, s_xmlns)) {
+        return 1;
+    }
+    line.ptr += 6;
+    line.length -= 6;
+    
+    
+    // format: <prefix>="<href>"
+    // find '='
+    size_t i;
+    size_t token_end = 0;
+    for(i=0;i<line.length;i++) {
+        if(line.ptr[i] == '=') {
+            token_end = i;
+            break;
+        }
+    }
+    if(token_end == 0) {
+        return 1;
+    }
+    *prefix = cx_strn(line.ptr, token_end);
+            
+    // make sure the line length is big enough
+    if(token_end + 4 > line.length) {
+        return 1;
+    }
+    if(line.ptr[token_end + 1] != '\"') {
+        return 1;
+    }
+    line.ptr += token_end + 2;
+    line.length -= token_end + 2;
+    
+    // get <href>
+    int escape = 0;
+    token_end = 0;
+    for(i=0;i<line.length;i++) {
+        if(line.ptr[i] == '\\') {
+            escape = 1;
+            continue;
+        } else if(!escape && line.ptr[i] == '\"') {
+            token_end = i;
+            break;
+        }
+        escape = 0;
+    }
+    if(token_end == 0) {
+        return 1;
+    }
+    *xmlns = cx_strn(line.ptr, token_end);
+    
+    // check length
+    if(token_end + 2 > line.length) {
+        return 1;
+    }
+    line.ptr += token_end + 2;
+    line.length -= token_end + 2;
+    *name = cx_strtrim(line);
+    
+    if(name->length == 0) {
+        return 1;
+    }
+    if(prefix->length == 0) {
+        return 1;
+    }
+    if(xmlns->length == 0) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int webdav_xattr_put_prop(CxMap *pmap, WebdavProperty *prop) {
+    CxHashKey key = webdav_property_key_a(
+            pmap->allocator,
+            (const char*)prop->namespace->href,
+            (const char*)prop->name);
+    if(!key.data.str) {
+        return 1;
+    }
+    int ret = cxMapPut(pmap, key, prop);
+    cxFree(pmap->allocator, key.data.str);
+    return ret;
+}
+
+CxMap* webdav_xattr_parse_data(CxAllocator *a, const char *data, size_t len, const char *path) {
+    CxMap *pmap = cxHashMapCreate(a, 32);
+    if(!pmap) {
+        return NULL;
+    }
+    cxstring dat = cx_strn(data, len);
+    
+    printf("\n\n");
+    
+    cxstring s_elm  = CX_STR("prop ");
+    cxstring s_ns   = CX_STR("ns ");
+    cxstring s_data = CX_STR("data ");
+    
+    WebdavProperty *prop = NULL;
+    
+    int error = 0;
+    
+    size_t elmno = 0;
+    cxstring line;
+    size_t pos = 0;
+    while(get_next_line(dat, &pos, &line)) {
+        if(cx_strprefix(line, s_elm)) {
+            if(prop) {
+                if(webdav_xattr_put_prop(pmap, prop)) {
+                    error = 1;
+                    break;
+                }
+            }
+            
+            line = cx_strsubs(line, 5);
+            
+            // get prop name, namespace and lang
+            cxstring name;
+            cxstring prefix;
+            cxstring xmlns;
+            cxstring lang;
+            if(webdav_xattr_parse_elm(line, &name, &prefix, &xmlns, &lang)) {
+                log_ereport(
+                        LOG_FAILURE,
+                        "webdav xattr backend: %file %s: invalid xattr format[%d]: cannot parse elm line",
+                        path,
+                        elmno);
+                error = 1;
+                break;
+            }
+            
+            // create property
+            prop = cxMalloc(a, sizeof(WebdavProperty));
+            if(!prop) {
+                error = 1;
+                break;
+            }
+            ZERO(prop, sizeof(WebdavProperty));
+            
+            WSNamespace *ns = cxMalloc(a, sizeof(WSNamespace));
+            if(!ns) {
+                error = 1;
+                break;
+            }
+            
+            char *name_str = cx_strdup_a(a, name).ptr;
+            char *prefix_str = cx_strdup_a(a, prefix).ptr;
+            char *xmlns_str = cx_strdup_a(a, xmlns).ptr;
+            if(!(name_str && prefix_str && xmlns_str)) {
+                error = 1;
+                break;
+            }
+            
+            ns->prefix = (const xmlChar*)prefix_str;
+            ns->href = (const xmlChar*)xmlns_str;
+            
+            prop->name = name_str;
+            prop->namespace = ns;
+            
+            elmno++;
+        } else if(prop) {
+            if(cx_strprefix(line, s_ns)) {
+                // TODO
+            } else if(cx_strprefix(line, s_data)) {
+                line = cx_strsubs(line, 5);
+                
+                // util_strtoint just works here, because the line ends with \n
+                // the xattr content data is also garanteed to be 0-terminated
+                int64_t data_len = 0;
+                if(!util_strtoint(line.ptr, &data_len)) {
+                    log_ereport(
+                            LOG_FAILURE,
+                            "webdav xattr backend: %file %s: invalid xattr format[%d]: number expected after data",
+                            path,
+                            elmno);
+                    error = 1;
+                    break;
+                }
+                
+                // get data
+                if(data_len > 0) {
+                    if(data_len > dat.length - pos) {
+                        log_ereport(
+                                LOG_FAILURE,
+                                "webdav xattr backend: %file %s: invalid data length %" PRId64 " in prop %d",
+                                path,
+                                data_len,
+                                elmno);
+                        error = 1;
+                        break;
+                    }
+                    
+                    cxstring propdata;
+                    propdata.ptr = dat.ptr + pos;
+                    propdata.length = data_len;
+                    pos += data_len;
+                    
+                    cxmutstr propdata_cp = cx_strdup_a(a, propdata);
+                    if(!propdata_cp.ptr) {
+                        error = 1;
+                        break;
+                    }
+                    prop->vtype = WS_VALUE_XML_DATA;
+                    prop->value.data.namespaces = NULL;
+                    prop->value.data.data = propdata_cp.ptr;
+                    prop->value.data.length = propdata_cp.length;
+                    
+                    if(pos < dat.length && dat.ptr[pos] == '\n') {
+                        pos++;
+                    }
+                }
+            } else {
+                log_ereport(
+                        LOG_FAILURE,
+                        "webdav xattr backend: %file %s: invalid xattr format[%d]: unknown element",
+                        path,
+                        elmno);
+                error = 1;
+                break;
+            }
+        }
+        
+        //printf("line: {%.*s}\n", (int)line.length, line.ptr);
+        //fflush(stdout);
+    }
+    
+    // add last property
+    if(prop) {
+        if(webdav_xattr_put_prop(pmap, prop)) {
+            error = 1;
+        }
+    }
+    
+    if(error) {
+        // TODO: free pmap content
+        cxMapDestroy(pmap);
+        pmap = NULL;
+    }
+    
+    //printf("\n\n");
+    //fflush(stdout);
+    
+    
+    return pmap;
+}
+
+cxmutstr webdav_xattr_serialze_map(CxAllocator *a, CxMap *pmap) {
+    pool_handle_t *pool = a->data;
+    
+    CxBuffer buf;
+    if(cxBufferInit(&buf, NULL, 8192, a, CX_BUFFER_AUTO_EXTEND)) {
+        return (cxmutstr){NULL,0};
+    }
+    
+    CxIterator i = cxMapIteratorValues(pmap);
+    cx_foreach(WebdavProperty*, prop, i) {
+        WSXmlData *property_value = NULL;
+        if(prop->vtype == WS_VALUE_XML_NODE) {
+            property_value = wsxml_node2data(pool, prop->value.node);
+        } else if(prop->vtype == WS_VALUE_XML_DATA) {
+            property_value = &prop->value.data;
+        }
+        
+        cx_bprintf(&buf, "prop xmlns:%s=\"%s\" %s\n", prop->namespace->prefix, prop->namespace->href, prop->name);
+        if(property_value) {
+            WebdavNSList *ns = property_value->namespaces; 
+            while(ns) {
+                cx_bprintf(&buf, "ns %s:%s\n", prop->namespace->prefix, prop->namespace->href);
+                ns = ns->next;
+            }
+            
+            cx_bprintf(&buf, "data %zu\n", property_value->length);
+            cxBufferWrite(property_value->data, 1, property_value->length, &buf);
+            cxBufferPut(&buf, '\n');
+        }
+    }
+    
+    return (cxmutstr){buf.space, buf.size};
+}
+
--- a/src/server/webdav/xattrbackend.h	Sat Mar 18 15:52:35 2023 +0100
+++ b/src/server/webdav/xattrbackend.h	Sat Mar 18 19:33:06 2023 +0100
@@ -32,6 +32,9 @@
 
 #include "../public/webdav.h"
 
+#include <cx/map.h>
+#include <cx/string.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -48,7 +51,12 @@
     const char *base_href;
     const char *base_path;
 } XAttrPropfind;
-    
+
+typedef struct XAttrProppatch {
+    CxMap *properties;
+} XAttrProppatch;
+
+
 int webdav_init_xattr_backend(void);
 
 
@@ -84,6 +92,15 @@
             WSBool commit);
 
 
+/* properties xattr data */
+
+int webdav_xattr_put_prop(CxMap *pmap, WebdavProperty *prop);
+
+CxMap* webdav_xattr_parse_data(CxAllocator *a, const char *data, size_t len, const char *path);
+
+cxmutstr webdav_xattr_serialze_map(CxAllocator *a, CxMap *pmap);
+
+
 #ifdef __cplusplus
 }
 #endif

mercurial