Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2023 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 "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 = { webdav_xattr_propfind_init, webdav_xattr_propfind_do, webdav_xattr_propfind_finish, webdav_xattr_proppatch_do, webdav_xattr_proppatch_finish, NULL, // opt_mkcol NULL, // opt_mkcol_finish NULL, // opt_delete NULL, // opt_delete_finish 0, // settings NULL, // instance NULL // next }; int webdav_init_xattr_backend(void) { if(webdav_register_backend("xattr", webdav_xattr_init, webdav_xattr_create)) { return 1; } return 0; } void* webdav_xattr_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { WebdavXAttrRepository *repo = pool_malloc(pool, sizeof(WebdavXAttrRepository)); if(!repo) { return NULL; } // TODO: config repo->xattr_name = "webdav_properties"; return repo; } WebdavBackend* webdav_xattr_create(Session *sn, Request *rq, pblock *pb, void *initData) { WebdavBackend *dav = pool_malloc(sn->pool, sizeof(WebdavBackend)); if(!dav) { return NULL; } WebdavXAttrBackend *instance = pool_malloc(sn->pool, sizeof(WebdavXAttrBackend)); if(!instance) { return NULL; } instance->repo = initData; *dav = webdav_xattr_backend; dav->instance = instance; return dav; } /* -------------------- webdav backend imlementation ----------------------*/ int webdav_xattr_propfind_init( WebdavPropfindRequest *rq, const char *path, const char *href, WebdavPList **outplist) { // make sure the sys vfs is used, because currently only // native sysfs xattr is supported if(rq->rq->vfs) { log_ereport(LOG_FAILURE, "webdav-propfind: xattr backend unsupported with non-native VFS"); return 1; } XAttrPropfind *xprop = pool_malloc(rq->sn->pool, sizeof(XAttrPropfind)); if(!xprop) { return 1; } rq->userdata = xprop; xprop->base_href = href; xprop->base_path = path; return 0; } int webdav_xattr_propfind_do( WebdavPropfindRequest *request, WebdavResponse *response, VFS_DIR parent, WebdavResource *resource, struct stat *s) { Session *sn = request->sn; Request *rq = request->rq; CxAllocator *a = pool_allocator(sn->pool); WebdavXAttrBackend *xdav = request->dav->instance; WebdavXAttrRepository *repo = xdav->repo; XAttrPropfind *xprop = request->userdata; // get resource path const char *path; char *path_dp = NULL; if(!parent) { // use base path path = xprop->base_path; } else { size_t base_href_len = strlen(xprop->base_href); size_t base_path_len = strlen(xprop->base_path); char *res_path = resource->href + base_href_len; size_t res_path_len = strlen(res_path); path_dp = pool_malloc(sn->pool, base_path_len + res_path_len + 2); memcpy(path_dp, xprop->base_path, base_path_len); int s = 0; if(path_dp[base_path_len-1] != '/' && res_path[0] != '/') { path_dp[base_path_len] = '/'; s = 1; } memcpy(path_dp + base_path_len + s, res_path, res_path_len); path_dp[base_path_len + s + res_path_len] = 0; path = path_dp; } 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); if(!xattr_data) { // no properties for this resource return 0; } CxMap *pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path); pool_free(sn->pool, xattr_data); if(!pmap) { return 1; } int err; if(request->allprop || request->propname) { err = webdav_xattr_propfind_allprop(request, resource, a, pmap); } else { err = webdav_xattr_propfind_get_requested_properties(request, resource, a, pmap); } return err; } int webdav_xattr_propfind_finish(WebdavPropfindRequest *rq) { return 0; } // helper functions for propfind_do int webdav_xattr_propfind_get_requested_properties( WebdavPropfindRequest *request, WebdavResource *resource, CxAllocator *a, CxMap *pmap) { WebdavPListIterator i = webdav_plist_iterator(&request->properties); WebdavPList *cur; while(webdav_plist_iterator_next(&i, &cur)) { WebdavProperty *reqprop = cur->property; CxHashKey key = webdav_property_key_a( a, (const char*)reqprop->namespace->href, (const char*)reqprop->name); if(!key.data) { return 1; } WebdavProperty *prop = cxMapGet(pmap, key); if(prop) { if(resource->addproperty(resource, prop, 200)) { return 1; } } } return 0; } int webdav_xattr_propfind_allprop( WebdavPropfindRequest *request, WebdavResource *resource, CxAllocator *a, CxMap *pmap) { CxIterator i = cxMapIteratorValues(pmap); cx_foreach(WebdavProperty*, prop, i) { if(request->propname) { prop->vtype = WS_VALUE_NO_TYPE; } if(resource->addproperty(resource, prop, 200)) { return 1; } } return 0; } int webdav_xattr_proppatch_do( WebdavProppatchRequest *request, WebdavResource *response, VFSFile *file, WebdavPList **setInOut, WebdavPList **removeInOut) { Session *sn = request->sn; Request *rq = request->rq; CxAllocator *a = pool_allocator(sn->pool); WebdavXAttrBackend *xdav = request->dav->instance; WebdavXAttrRepository *repo = xdav->repo; char *path = pblock_findkeyval(pb_key_path, rq->vars); // How xattr proppatch works: // 1. get existing property data from the file's xattr // 2. do set/remove operations to the existing data // 3. write new property data to the xattr (webdav_xattr_proppatch_finish) // TODO: proppatch file lock (in-process userspace lock) XAttrProppatch *xprop = pool_malloc(sn->pool, sizeof(XAttrProppatch)); if(!xprop) { return 1; } request->userdata = xprop; // 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); pool_free(sn->pool, xattr_data); } else { // empty map pmap = cxHashMapCreate(a, CX_STORE_POINTERS, request->setcount + 8); } if(!pmap) { return 1; } xprop->properties = pmap; // 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) { cxMapDestroy(pmap); return 1; } void *rmprop = cxMapRemoveAndGet(pmap, key); cxFree(a, (void*)key.data); // 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); } return 0; } int webdav_xattr_proppatch_finish( WebdavProppatchRequest *request, WebdavResource *response, VFSFile *file, WSBool commit) { 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 = request->userdata; char *path = pblock_findkeyval(pb_key_path, rq->vars); int ret = 0; if(commit) { cxmutstr new_data = webdav_xattr_serialze_map(a, xprop->properties); if(new_data.ptr) { if(xattr_set(path, repo->xattr_name, new_data.ptr, new_data.length)) { ret = 1; } } else { ret = 1; } } 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; } static int webdav_xattr_parse_ns(cxstring line, cxstring *prefix, cxstring *href) { size_t i = 0; for(i=1;i<line.length;i++) { if(line.ptr[i] == ':') { break; } } if(i == 0 || i+1 >= line.length) { // error: ':' not found or ':' is the last character return 1; } cxstring pre; pre.ptr = line.ptr; pre.length = i; *prefix = pre; *href = cx_strsubs(line, i+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) { return 1; } int ret = cxMapPut(pmap, key, prop); cxFree(pmap->allocator, (void*)key.data); return ret; } CxMap* webdav_xattr_parse_data(CxAllocator *a, const char *data, size_t len, const char *path) { CxMap *pmap = cxHashMapCreate(a, CX_STORE_POINTERS, 32); if(!pmap) { return NULL; } cxstring dat = cx_strn(data, len); cxstring s_elm = CX_STR("prop "); cxstring s_ns = CX_STR("ns "); cxstring s_data = CX_STR("data "); WebdavProperty *prop = NULL; WebdavNSList *ns_begin = NULL; WebdavNSList *ns_end = 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)); ns_begin = NULL; ns_end = NULL; WSNamespace *ns = cxMalloc(a, sizeof(WSNamespace)); if(!ns) { error = 1; break; } ZERO(ns, sizeof(WSNamespace)); 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)) { line = cx_strsubs(line, 3); cxstring prefix; cxstring href; if(webdav_xattr_parse_ns(line, &prefix, &href)) { log_ereport( LOG_FAILURE, "webdav xattr backend: file %s: invalid xattr format[%d]: cannot parse ns", path, elmno); error = 1; break; } WSNamespace *ns_decl = cxMalloc(a, sizeof(WSNamespace)); if(!ns_decl) { error = 1; break; } ZERO(ns_decl, sizeof(WSNamespace)); ns_decl->prefix = (const xmlChar*)cx_strdup_a(a, prefix).ptr; ns_decl->href = (const xmlChar*)cx_strdup_a(a, href).ptr; if(!(ns_decl->prefix && ns_decl->href)) { error = 1; break; } if(webdav_nslist_add(a->data, &ns_begin, &ns_end, ns_decl)) { error = 1; break; } } 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 = ns_begin; 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; } } } // add last property if(prop) { if(webdav_xattr_put_prop(pmap, prop)) { error = 1; } } if(error) { // TODO: free pmap content cxMapDestroy(pmap); pmap = NULL; } 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; } int ret = cx_bprintf(&buf, "prop xmlns:%s=\"%s\" %s\n", prop->namespace->prefix, prop->namespace->href, prop->name); if(ret <= 0) { pool_free(pool, buf.space); return (cxmutstr){NULL,0}; } if(property_value) { WebdavNSList *ns = property_value->namespaces; while(ns) { ret = cx_bprintf(&buf, "ns %s:%s\n", ns->namespace->prefix, ns->namespace->href); if(ret <= 0) { pool_free(pool, buf.space); return (cxmutstr){NULL,0}; } ns = ns->next; } ret = cx_bprintf(&buf, "data %zu\n", property_value->length); if(ret <= 0) { pool_free(pool, buf.space); return (cxmutstr){NULL,0}; } cxBufferWrite(property_value->data, 1, property_value->length, &buf); if(cxBufferPut(&buf, '\n') < 0) { pool_free(pool, buf.space); return (cxmutstr){NULL,0}; } } } return (cxmutstr){buf.space, buf.size}; }