--- a/src/server/plugins/postgresql/webdav.c Thu Apr 21 17:16:49 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.c Sun Apr 24 18:35:44 2022 +0200 @@ -27,6 +27,11 @@ */ #include "webdav.h" +#include "vfs.h" + +#include "../../util/util.h" + +#include <libxml/tree.h> static WebdavBackend pg_webdav_backend = { pg_dav_propfind_init, @@ -43,6 +48,162 @@ NULL }; + +/* + * SQL Queries + */ + +// propfind with depth = 0 +// params: $1: resource_id +static const char *sql_propfind_allprop_depth0 = "\ +select\n\ + NULL as ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ +from Resource r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +where r.resource_id = $1;"; + +// propfind with depth = 0 for specific properties +// params: $1: resource_id +static const char *sql_propfind_depth0 = "\ +select\n\ + NULL as ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ +from Resource r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +inner join (select unnest($2::text[]) as xmlns, unnest($3::text[]) as pname) n\n\ + on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\ +where r.resource_id = $1;"; + +// propfind with depth = 1 +// params: $1: resource_id +static const char *sql_propfind_allprop_depth1 = "\ +select\n\ + NULL ass ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ +from Resource r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +where r.resource_id = $1 or r.parent_id = $1\n\ +order by order by case when resource_id = $1 then 0 else 1 end, nodename, resource_id;"; + + +// propfind with depth = 1 for specific properties +// params: $1: resource_id +static const char *sql_propfind_depth1 = "\ +select\n\ + NULL as ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ +from Resource r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +inner join (select unnest($2::text[]) as xmlns, unnest($3::text[]) as pname) n\n\ + on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\ +where r.resource_id = $1 or r.parent_id = $1\n\ +order by order by case when resource_id = $1 then 0 else 1 end, nodename, resource_id;"; + +// recursive propfind +// params: $1: resource_id +static const char *sql_propfind_allprop_recursive = "\ +with recursive resolvepath as (\n\ + select\n\ + '' as ppath,\n\ + *\n\ + from Resource\n\ + where resource_id = $1 \n\ + union\n\ + select\n\ + p.ppath || '/' || r.nodename,\n\ + r.*\n\ + from Resource r\n\ + inner join resolvepath p on r.parent_id = p.resource_id\n\ + )\n\ +select\n\ + r.ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ + from resolvepath r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +order by replace(ppath, '/', chr(1)), resource_id;"; + +// recursive propfind for specific properties +// params: $1: resource_id +// $2: array of property xmlns +// $3: array of property names +static const char *sql_propfind_recursive = "\ +with recursive resolvepath as (\n\ + select\n\ + '' as ppath,\n\ + *\n\ + from Resource\n\ + where resource_id = $1 \n\ + union\n\ + select\n\ + p.ppath || '/' || r.nodename,\n\ + r.*\n\ + from Resource r\n\ + inner join resolvepath p on r.parent_id = p.resource_id\n\ + )\n\ +select\n\ + r.ppath,\n\ + r.resource_id,\n\ + r.parent_id,\n\ + r.nodename,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + p.xmlns,\n\ + p.pname,\n\ + p.pvalue\n\ +from resolvepath r\n\ +left join Property p on r.resource_id = p.resource_id\n\ +inner join (select unnest($2::text[]) as xmlns, unnest($3::text[]) as pname) n\n\ + on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\ +order by replace(ppath, '/', chr(1)), resource_id;"; + WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb) { // resourcepool is required char *resource_pool = pblock_findval("resourcepool", pb); @@ -69,7 +230,7 @@ *webdav = pg_webdav_backend; PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend)); - if(instance) { + if(!instance) { pool_free(sn->pool, webdav); return NULL; } @@ -91,10 +252,75 @@ const char *path, WebdavPList **outplist) { + PgWebdavBackend *pgdav = rq->dav->instance; + + // check if the resource exists + int64_t parent_id; + int64_t resource_id; + const char *resourcename; + WSBool iscollection; + int res_errno = 0; + int err = pg_resolve_path( + pgdav->connection, + path, + &parent_id, + &resource_id, + NULL, // OID + &resourcename, + &iscollection, + NULL, // stat + &res_errno); + + if(err) { + if(res_errno == ENOENT) { + protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL); + } + return 1; + } + + // choose sql query + const char *query = NULL; + if(!iscollection || rq->depth == 0) { + query = rq->allprop ? sql_propfind_allprop_depth0 : sql_propfind_depth0; + } else if(rq->depth == 1) { + query = rq->allprop ? sql_propfind_allprop_depth1 : sql_propfind_depth1; + } else if(rq->depth == -1) { + query = rq->allprop ? sql_propfind_allprop_recursive : sql_propfind_recursive; + } else { + log_ereport(LOG_FAILURE, "%s", "pg_dav_propfind_init: invalid depth"); + return 1; + } + + // get all resources and properties + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, resource_id); + + const char* params[1] = { resource_id_str }; + PGresult *result = PQexecParams( + pgdav->connection, + sql_propfind_allprop_recursive, + 1, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + int nrows = PQntuples(result); + if(nrows < 1) { + PQclear(result); + return 1; + } + PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind)); rq->userdata = pg; - return 1; + pg->path = path; + pg->resource_id = resource_id; + pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, 0); + pg->result = result; + pg->nrows = nrows; + + return 0; } int pg_dav_propfind_do( @@ -104,11 +330,103 @@ WebdavResource *resource, struct stat *s) { - return 1; + PgPropfind *pg = rq->userdata; + pool_handle_t *pool = rq->sn->pool; + PGresult *result = pg->result; + WebdavVFSProperties vfsprops = pg->vfsproperties; + + WSBool vfsprops_set = 0; + int64_t current_resource_id = pg->resource_id; + for(int r=0;r<pg->nrows;r++) { + // columns: + // 0: path + // 1: resource_id + // 2: parent_id + // 3: nodename + // 4: iscollection + // 5: lastmodified + // 6: creationdate + // 7: contentlength + // 8: property xmlns + // 9: property name + // 10: property value + + char *path = PQgetvalue(result, r, 0); + char *res_id = PQgetvalue(result, r, 1); + int64_t resource_id; + if(!util_strtoint(res_id, &resource_id)) { + log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id); + return 1; + } + + char *nodename = PQgetvalue(result, r, 3); + if(resource_id != current_resource_id) { + // new resource + resource = response->addresource(response, nodename); + vfsprops_set = FALSE; + current_resource_id = resource_id; + } + + // standard webdav properties + if(!vfsprops_set) { + if(vfsprops.getresourcetype) { + char *iscollection = PQgetvalue(result, r, 4); + if(iscollection && iscollection[0] == 't') { + resource->addproperty(resource, webdav_resourcetype_collection(), 200); + } else { + resource->addproperty(resource, webdav_resourcetype_empty(), 200); + } + } + if(vfsprops.getlastmodified) { + char *lastmodified = PQgetvalue(result, r, 5); + } + if(vfsprops.creationdate) { + char *creationdate = PQgetvalue(result, r, 6); + } + if(vfsprops.getcontentlength) { + char *contentlength = PQgetvalue(result, r, 7); + } + if(vfsprops.getetag) { + + } + + + vfsprops_set = TRUE; + } + + // dead properties + if(!PQgetisnull(result, r, 9)) { + char *xmlns = PQgetvalue(result, r, 8); + char *pname = PQgetvalue(result, r, 9); + char *pvalue = PQgetvalue(result, r, 10); + + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + property->lang = NULL; + property->name = pool_strdup(pool, pname); + + xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs)); + memset(namespace, 0, sizeof(struct _xmlNs)); + namespace->href = pool_strdup(pool, xmlns); + namespace->prefix = "zx1"; + property->namespace = namespace; + + property->vtype = WS_VALUE_TEXT; + property->value.text.str = pool_strdup(pool, pvalue); + property->value.text.length = strlen(pvalue); + + resource->addproperty(resource, property, 200); + } + + + } + + + + return 0; } int pg_dav_propfind_finish(WebdavPropfindRequest *rq) { - return 1; + return 0; } int pg_dav_proppatch_do(