Sat, 18 Mar 2023 19:33:06 +0100
implement proppatch of the xattr webdav backend
--- 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