Sun, 14 Aug 2022 16:46:52 +0200
Add support for extended properties in pg propfind
--- 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;