Add support for extended properties in pg propfind webdav

Sun, 14 Aug 2022 16:46:52 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 14 Aug 2022 16:46:52 +0200
branch
webdav
changeset 376
61d481d3c2e4
parent 375
32b8017f5308
child 377
c011bc2b3143

Add support for extended properties in pg propfind

src/server/plugins/postgresql/config.c file | annotate | diff | comparison | revisions
src/server/plugins/postgresql/webdav.c file | annotate | diff | comparison | revisions
src/server/plugins/postgresql/webdav.h file | annotate | diff | comparison | revisions
src/server/public/webdav.h file | annotate | diff | comparison | revisions
src/server/test/webdav.c file | annotate | diff | comparison | revisions
src/server/webdav/multistatus.c file | annotate | diff | comparison | revisions
src/server/webdav/webdav.c file | annotate | diff | comparison | revisions
--- a/src/server/plugins/postgresql/config.c	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/plugins/postgresql/config.c	Sun Aug 14 16:46:52 2022 +0200
@@ -359,31 +359,31 @@
     if(!exttable) return 1;
     exttable->table = tabname;
     exttable->isused = 0; // not relevant in config
+    int tableindex = (int)ucx_list_size(ext->tables);
     ext->tables = ucx_list_append(ext->tables, exttable);
     
     if(ucx_map_cstr_put(ext->table_lookup, table, table)) {
         return 1;
     }
-    
-    int tableindex = (int)ucx_list_size(ext->tables);
+     
     UCX_FOREACH(elm, properties) {
         xmlNode *ps = elm->data;
         const char *value = pg_util_xml_get_text(ps);
         
-        PgPropertyStoreExt *ext = pool_malloc(pool, sizeof(PgPropertyStoreExt));
-        if(!ext) {
+        PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt));
+        if(!ext_col) {
             return 1;
         }
-        ext->tableindex = tableindex;
-        ext->ns = pool_strdup(pool, (const char*)ps->ns->href);
-        ext->name = pool_strdup(pool, (const char*)ps->name);
-        ext->column = pool_strdup(pool, (const char*)value);
-        if(!ext->ns || !ext->name || !ext->column) {
+        ext_col->tableindex = tableindex;
+        ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href);
+        ext_col->name = pool_strdup(pool, (const char*)ps->name);
+        ext_col->column = pool_strdup(pool, (const char*)value);
+        if(!ext_col->ns || !ext_col->name || !ext_col->column) {
             return 1;
         }
         
-        UcxKey key = webdav_property_key(ext->ns, ext->name);
-        int err = ucx_map_put(repo->prop_ext, key, ext);
+        UcxKey key = webdav_property_key(ext_col->ns, ext_col->name);
+        int err = ucx_map_put(repo->prop_ext, key, ext_col);
         free((void*)key.data);
         if(err) {
             return 1;
--- a/src/server/plugins/postgresql/webdav.c	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/plugins/postgresql/webdav.c	Sun Aug 14 16:46:52 2022 +0200
@@ -36,6 +36,7 @@
 #include "../../daemon/http.h" // etag
 
 #include <ucx/buffer.h>
+#include <ucx/utils.h>
 #include <libxml/tree.h>
 
 
@@ -277,8 +278,27 @@
 }
 
 
-static int pg_create_propfind_query(WebdavPropfindRequest *rq, WSBool iscollection, UcxBuffer *sql) {
+static int propfind_ext_cmp(const void *f1, const void *f2) {
+    const PgPropfindExtCol *e1 = f1;
+    const PgPropfindExtCol *e2 = f2;
+    
+    if(e1->ext->tableindex != e2->ext->tableindex) {
+        return e1->ext->tableindex < e2->ext->tableindex ? -1 : 1;
+    }
+    
+    return 0;
+}
+
+
+static int pg_create_propfind_query(
+        WebdavPropfindRequest *rq,
+        WSBool iscollection,
+        PgPropfindExtCol *ext,
+        size_t numext,
+        UcxBuffer *sql)
+{
     PgWebdavBackend *pgdav = rq->dav->instance;
+    PgRepository *repo = pgdav->repository;
     int depth = !iscollection ? 0 : rq->depth;
     
     /*
@@ -314,12 +334,45 @@
     // cols
     ucx_buffer_puts(sql, sql_propfind_cols);
     
+    // ext_cols
+    if(ext) { 
+        if(rq->allprop) {
+            for(int i=0;i<repo->ntables;i++) {
+                ucx_bprintf(sql, ",x%d.*\n", i);
+            }
+        } else {
+            for(int i=0;i<numext;i++) {
+                PgPropfindExtCol e = ext[i];
+                ucx_bprintf(sql, ",x%d.%s\n", e.ext->tableindex, e.ext->column);
+            }
+        }
+    }
+    
     // from
     ucx_buffer_puts(sql, depth == -1 ? sql_propfind_from_cte : sql_propfind_from_table);
     
     // prop join
     ucx_buffer_puts(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist);
     
+    // ext_join
+    if(ext) {
+        if(rq->allprop) {
+            for(int i=0;i<repo->ntables;i++) {
+                ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[i].table, i, i);
+            }
+        } else {
+            int tab = -1;
+            for(int i=0;i<numext;i++) {
+                PgPropfindExtCol e = ext[i];
+                if(e.ext->tableindex != tab) {
+                    tab = e.ext->tableindex;
+                    ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[tab].table, tab, tab);
+                }
+            }
+        }
+        
+    }
+    
     // where
     if(depth == 0) {
         ucx_buffer_puts(sql, sql_propfind_where_depth0);
@@ -337,9 +390,6 @@
     // end
     ucx_buffer_puts(sql, ";\0");
     
-    //printf("\n\n%s\n\n", sql->space);
-    //fflush(stdout);
-    
     return 0;
 }
 
@@ -351,7 +401,8 @@
 {
     PgWebdavBackend *pgdav = rq->dav->instance;
     
-    // check if the resource exists
+    // first, check if the resource exists
+    // if it doesn't exist, we can return immediately
     int64_t parent_id;
     int64_t resource_id;
     const char *resourcename;
@@ -377,10 +428,60 @@
         return 1;
     }
     
+    // create a list of requsted extended properties
+    PgPropfindExtCol *ext;
+    size_t numext;
+    if(pgdav->repository->ntables == 0) {
+        // no property extensions configured
+        ext = NULL;
+        numext = 0;
+    } else {     
+        numext = pgdav->repository->prop_ext->count;
+        ext = pool_calloc(rq->sn->pool, numext, sizeof(PgPropfindExtCol));
+        
+        if(rq->allprop) {
+            // the map pgdav->repository->prop_ext contains all property extensions
+            // we can just convert the map to an array
+            UcxMapIterator i = ucx_map_iterator(pgdav->repository->prop_ext);
+            PgPropertyStoreExt *cfg_ext;
+            int j = 0;
+            UCX_MAP_FOREACH(key, cfg_ext, i) {
+                PgPropfindExtCol extcol;
+                extcol.ext = cfg_ext;
+                extcol.field_num = -1; // get the field_num after the PQexec
+                ext[j++] = extcol;
+            }
+        } else {
+            WebdavPListIterator i = webdav_plist_iterator(outplist);
+            WebdavPList *cur;
+            int j = 0;
+            while(webdav_plist_iterator_next(&i, &cur)) {
+                WSNamespace *ns = cur->property->namespace;
+                if(ns) {
+                    UcxKey pkey = webdav_property_key((const char*)ns->href, cur->property->name);
+                    PgPropertyStoreExt *cfg_ext = ucx_map_get(pgdav->repository->prop_ext, pkey);
+                    char *pkey_data = pkey.data;
+                    free((void*)pkey.data);
+                    if(cfg_ext) {
+                        PgPropfindExtCol extcol;
+                        extcol.ext = cfg_ext;
+                        extcol.field_num = -1; // get the field_num after the PQexec
+                        ext[j++] = extcol;
+                        
+                        webdav_plist_iterator_remove_current(&i);
+                    }
+                }
+            }
+            numext = j;
+        }
+        
+        qsort(ext, numext, sizeof(PgPropfindExtCol), propfind_ext_cmp);
+    }
+    
     // create sql query
     const char *query = NULL;
     UcxBuffer *sql = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND);
-    if(pg_create_propfind_query(rq, iscollection, sql)) {
+    if(pg_create_propfind_query(rq, iscollection, ext, numext, sql)) {
         return 1;
     }
     query = sql->space;
@@ -414,7 +515,7 @@
             ucx_buffer_free(xmlns_buf);
             return 1;
         }
-        if(pg_create_property_param_arrays(rq->properties, xmlns_buf, pname_buf)) {
+        if(pg_create_property_param_arrays(*outplist, xmlns_buf, pname_buf)) {
             ucx_buffer_free(xmlns_buf);
             ucx_buffer_free(pname_buf);
             return 1;
@@ -454,6 +555,16 @@
     pg->result = result;
     pg->nrows = nrows;
     
+    pg->ext = ext;
+    pg->numext = numext;
+    if(ext) {
+        // get field_nums for all property extensions
+        for(int i=0;i<numext;i++) {
+            PgPropfindExtCol *c = &ext[i];
+            c->field_num = PQfnumber(result, c->ext->column);
+        }
+    }
+    
     return 0;
 }
 
@@ -470,6 +581,7 @@
     WebdavVFSProperties vfsprops = pg->vfsproperties;
     
     WSBool vfsprops_set = 0; // are live properties added to the response?
+    WSBool extprops_set = 0; // are extended properties added to the response?
     int64_t current_resource_id = pg->resource_id;
     for(int r=0;r<pg->nrows;r++) {
         // columns:
@@ -571,6 +683,51 @@
             vfsprops_set = TRUE;
         }
         
+        if(!extprops_set) {
+            // extended properties
+            if(pg->ext) {
+                for(int extc=0;extc<pg->numext;extc++) {
+                    PgPropfindExtCol ext = pg->ext[extc];
+                    int fieldnum = ext.field_num;
+                    
+                    if(!PQgetisnull(result, r, fieldnum)) {
+                        char *ext_value = PQgetvalue(result, r, fieldnum);
+                        int ext_value_len = PQgetlength(result, r, fieldnum);
+                        char ext_xmlns_prefix[32];
+                        snprintf(ext_xmlns_prefix, 32, "x%d", ext.ext->tableindex);
+
+                        WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
+                        property->lang = NULL;
+                        property->name = pool_strdup(pool, ext.ext->name);
+
+                        xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
+                        memset(namespace, 0, sizeof(struct _xmlNs));
+                        namespace->href = (xmlChar*)pool_strdup(pool, ext.ext->ns);
+                        namespace->prefix = (xmlChar*)pool_strdup(pool, ext_xmlns_prefix);
+                        property->namespace = namespace;
+
+                        char *content = pool_malloc(pool, ext_value_len+1);
+                        memcpy(content, ext_value, ext_value_len);
+                        content[ext_value_len] = '\0';
+                        
+                        WebdavNSList *nslist = pool_malloc(pool, sizeof(WebdavNSList));
+                        nslist->namespace = namespace;
+                        nslist->prev = NULL;
+                        nslist->next = NULL;
+                        
+                        property->vtype = WS_VALUE_XML_DATA;               
+                        property->value.data.data = content;
+                        property->value.data.length = ext_value_len;
+                        property->value.data.namespaces = nslist;
+
+                        resource->addproperty(resource, property, 200);
+                    }
+                }
+            }
+            
+            extprops_set = TRUE;
+        }
+        
         // dead properties
         if(!PQgetisnull(result, r, 9)) {
             char *prefix = PQgetvalue(result, r, 9);
@@ -609,11 +766,11 @@
                     property->value.text.str = content;
                     property->value.text.length = pvalue_len;
                 } else {
-                    WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef); 
+                    WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef);
                     property->vtype = WS_VALUE_XML_DATA;
-                    property->value.data->data = content;
-                    property->value.data->length = pvalue_len;
-                    property->value.data->namespaces = nslist;
+                    property->value.data.data = content;
+                    property->value.data.length = pvalue_len;
+                    property->value.data.namespaces = nslist;
                     
                 }
             }
--- a/src/server/plugins/postgresql/webdav.h	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/plugins/postgresql/webdav.h	Sun Aug 14 16:46:52 2022 +0200
@@ -49,12 +49,25 @@
     PgRepository *repository;
     char root_resource_id_str[32];
 } PgWebdavBackend;
+
+typedef struct PgPropfindExtCol {
+    /*
+     * property extension config
+     */
+    PgPropertyStoreExt *ext;
+    /*
+     * Result field number
+     */
+    int field_num;
+} PgPropfindExtCol;
     
 typedef struct PgPropfind {
     const char *path;
     int64_t resource_id;
     WebdavVFSProperties vfsproperties;
     PGresult *result;
+    PgPropfindExtCol *ext;
+    size_t numext;
     int nrows;
 } PgPropfind;
 
--- a/src/server/public/webdav.h	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/public/webdav.h	Sun Aug 14 16:46:52 2022 +0200
@@ -107,6 +107,12 @@
     size_t length;
 };
 
+struct WSXmlData {
+    WebdavNSList *namespaces;
+    char         *data;
+    size_t       length;
+};
+
 struct WebdavProperty {
     WSNamespace *namespace;
     
@@ -116,18 +122,12 @@
     
     union {
         WSXmlNode *node;
-        WSXmlData *data;
+        WSXmlData data;
         WSText    text;
     } value;
     WebdavValueType vtype;
 };
 
-struct WSXmlData {
-    WebdavNSList *namespaces;
-    char         *data;
-    size_t       length;
-};
-
 struct WebdavPList {
     WebdavProperty *property;
     WebdavPList *prev;
--- a/src/server/test/webdav.c	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/test/webdav.c	Sun Aug 14 16:46:52 2022 +0200
@@ -929,7 +929,7 @@
     p1.namespace = webdav_dav_namespace();
     p1.lang = NULL;
     p1.name = "test1";
-    p1.value.data = NULL;
+    p1.value.data = (WSXmlData){ NULL, NULL, 0};
     p1.vtype = 0;
     
     for(int i=0;i<8;i++) {
--- a/src/server/webdav/multistatus.c	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/webdav/multistatus.c	Sun Aug 14 16:46:52 2022 +0200
@@ -204,8 +204,8 @@
                 // only write data, data->namespaces is already handled
                 writer_put(
                         out,
-                        property->value.data->data,
-                        property->value.data->length);
+                        property->value.data.data,
+                        property->value.data.length);
                 break;
             }
             case WS_VALUE_TEXT: {
@@ -341,6 +341,9 @@
     // end multistatus
     writer_puts(out, S("</D:multistatus>\n"));
     
+    //printf("\n\n%.*s\n\n", (int)writer.pos, writer.buffer);
+    //fflush(stdout);
+    
     writer_flush(out);
     
     return 0;
@@ -524,7 +527,7 @@
         }
     } else if(property->vtype == WS_VALUE_XML_DATA) {
         // xml data contains a list of all used namespaces
-        nslist = property->value.data->namespaces;
+        nslist = property->value.data.namespaces;
     } // other value types don't contain xml namespaces
     
     while(nslist) {
--- a/src/server/webdav/webdav.c	Sun Aug 14 12:43:14 2022 +0200
+++ b/src/server/webdav/webdav.c	Sun Aug 14 16:46:52 2022 +0200
@@ -173,7 +173,7 @@
     
     dav_resourcetype_collection.namespace = &dav_namespace;
     dav_resourcetype_collection.name = "resourcetype";
-    dav_resourcetype_collection.value.data = &dav_resourcetype_collection_value;
+    dav_resourcetype_collection.value.data = dav_resourcetype_collection_value;
     dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA;
     dav_resourcetype_collection_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION;
     dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1;

mercurial