Tue, 26 Apr 2022 14:33:58 +0200
fix sql query for selecting specific properties
--- a/src/server/plugins/postgresql/pgtest.c Mon Apr 25 22:38:05 2022 +0200 +++ b/src/server/plugins/postgresql/pgtest.c Tue Apr 26 14:33:58 2022 +0200 @@ -203,7 +203,8 @@ void test_multistatus_destroy(TestMultistatus *ms) { - + xmlFreeDoc(ms->doc); + ucx_mempool_destroy(ms->mp); } @@ -497,6 +498,13 @@ UCX_TEST_ASSERT(f1, "res3 create failed"); vfs_close(f1); + int r = vfs_mkdir(vfs, "/propfind/sub"); + UCX_TEST_ASSERT(r == 0, "sub create failed"); + + f1 = vfs_open(vfs, "/propfind/sub/res4", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "res4 create failed"); + vfs_close(f1); + // 2 properties for res1 char idstr[32]; snprintf(idstr, 32, "%" PRId64, res1_id); @@ -566,6 +574,7 @@ // Test 1 init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND1); rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "0", rq->headers); ret = webdav_propfind(pb, sn, rq); @@ -577,16 +586,70 @@ TestResponse *r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response"); + UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count"); + TestProperty *p = ucx_map_cstr_get(r1->properties, "D:resourcetype"); UCX_TEST_ASSERT(p, "propfind1: missing property 'resourcetype'"); UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'resourcetype'"); p = ucx_map_cstr_get(r1->properties, "D:getlastmodified"); UCX_TEST_ASSERT(p, "propfind1: missing property 'getlastmodified'"); - UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'getlastmodified'"); + UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'getlastmodified'"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + // Test 2 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "1", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response"); + + UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/res2"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res2 response"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + + // Test 3 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "infinity", rq->headers); + + ret = webdav_propfind(pb, sn, rq); printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (3) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response"); + + UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/sub/res4"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res4 response"); testutil_destroy_session(sn); test_multistatus_destroy(ms);
--- a/src/server/plugins/postgresql/pgtest.h Mon Apr 25 22:38:05 2022 +0200 +++ b/src/server/plugins/postgresql/pgtest.h Tue Apr 26 14:33:58 2022 +0200 @@ -63,7 +63,7 @@ /* --------------------------- PROPFIND --------------------------- */ #define PG_TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ - <D:propfind xmlns:D=\"DAV:\"> \ + <D:propfind xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" > \ <D:prop> \ <D:displayname/> \ <D:getcontentlength/> \ @@ -75,6 +75,21 @@ </D:prop> \ </D:propfind>" +#define PG_TEST_PROPFIND2 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \ + <D:propfind xmlns:D=\"DAV:\" xmlns:X=\"http://example.com/\" > \ + <D:prop> \ + <D:displayname/> \ + <D:getcontentlength/> \ + <D:getcontenttype/> \ + <D:getlastmodified/> \ + <D:creationdate/> \ + <D:resourcetype/> \ + <D:getetag/> \ + <X:test /> \ + <X:prop2 /> \ + </D:prop> \ + </D:propfind>" + #ifdef __cplusplus }
--- a/src/server/plugins/postgresql/webdav.c Mon Apr 25 22:38:05 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.c Tue Apr 26 14:33:58 2022 +0200 @@ -31,6 +31,7 @@ #include "../../util/util.h" +#include <ucx/buffer.h> #include <libxml/tree.h> static WebdavBackend pg_webdav_backend = { @@ -88,9 +89,11 @@ 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($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ - on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\ +left join (\n\ + select p.* from Property p\ + inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ + on p.xmlns = n.xmlns and p.pname = n.pname\n\ +) p on r.resource_id = p.resource_id\n\ where r.resource_id = $1;"; // propfind with depth = 1 @@ -113,7 +116,7 @@ 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;"; +order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id;"; // propfind with depth = 1 for specific properties @@ -134,11 +137,13 @@ 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($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ - on ( p.xmlns = n.xmlns and p.pname = n.pname ) or p.property_id is null\n\ +left join (\n\ + select p.* from Property p\ + inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ + on p.xmlns = n.xmlns and p.pname = n.pname\n\ +) 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;"; +order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id;"; // recursive propfind // params: $1: resource_id @@ -194,7 +199,7 @@ )\n\ select\n\ case when r.resource_id = $1 then $2\n\ - else $2 || '/' || r.ppath\n\ + else $2 || r.ppath\n\ end as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ @@ -207,9 +212,11 @@ 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\ +left join (\n\ + select p.* from Property p\ + inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ + on p.xmlns = n.xmlns and p.pname = n.pname\n\ +) p on r.resource_id = p.resource_id\n\ order by replace(ppath, '/', chr(1)), resource_id;"; WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb) { @@ -254,6 +261,49 @@ return NULL; } +/* + * adds str to the buffer + * some characters will be escaped: \,{} + */ +static void buf_addstr_escaped(UcxBuffer *buf, const char *str) { + size_t len = strlen(str); + for(size_t i=0;i<len;i++) { + char c = str[i]; + if(c == '{' || c == '}' || c == ',' || c == '\\') { + ucx_buffer_putc(buf, '\\'); + } + ucx_buffer_putc(buf, c); + } +} + +/* + * convert a property list to two pg array parameter strings + * array format: {elm1,elm2,elm3} + * xmlns: buffer for the xmlns array + * pname: buffer for the property name array + * + * returns 0 on success, 1 otherwise + */ +int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname) { + ucx_buffer_putc(xmlns, '{'); + ucx_buffer_putc(pname, '{'); + while(plist) { + WebdavProperty *property = plist->property; + if(property && property->namespace && property->namespace->href && property->name) { + buf_addstr_escaped(xmlns, (const char*)property->namespace->href); + buf_addstr_escaped(pname, (const char*)property->name); + if(plist->next) { + ucx_buffer_putc(xmlns, ','); + ucx_buffer_putc(pname, ','); + } + } + plist = plist->next; + } + int r1 = ucx_buffer_write("}\0", 2, 1, xmlns) == 0; + int r2 = ucx_buffer_write("}\0", 2, 1, pname) == 0; + return r1+r2 != 0; +} + int pg_dav_propfind_init( WebdavPropfindRequest *rq, @@ -312,12 +362,38 @@ } href_param[href_len] = '\0'; + // if allprop is false, create array pair for xmlns/property names + UcxBuffer *xmlns_buf = NULL; + UcxBuffer *pname_buf = NULL; + char *xmlns_param = NULL; + char *pname_param = NULL; + int nparam = 2; + if(!rq->allprop) { + size_t bufsize = rq->propcount < 200 ? 8 + rq->propcount * 32 : 4096; + xmlns_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); + if(!xmlns_buf) { + return 1; + } + pname_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); + if(!pname_buf) { + ucx_buffer_free(xmlns_buf); + return 1; + } + if(pg_create_property_param_arrays(rq->properties, xmlns_buf, pname_buf)) { + ucx_buffer_free(xmlns_buf); + ucx_buffer_free(pname_buf); + return 1; + } + xmlns_param = xmlns_buf->space; + pname_param = pname_buf->space; + nparam = 4; + } - const char* params[2] = { resource_id_str, href_param }; + const char* params[4] = { resource_id_str, href_param, xmlns_param, pname_param }; PGresult *result = PQexecParams( pgdav->connection, - sql_propfind_allprop_recursive, - 2, // number of parameters + query, + nparam, // number of parameters NULL, params, // parameter value NULL, @@ -325,6 +401,10 @@ 0); // 0: result in text format int nrows = PQntuples(result); pool_free(rq->sn->pool, href_param); + if(xmlns_buf) { + ucx_buffer_free(xmlns_buf); + ucx_buffer_free(pname_buf); + } if(nrows < 1) { PQclear(result); return 1;
--- a/src/server/plugins/postgresql/webdav.h Mon Apr 25 22:38:05 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.h Tue Apr 26 14:33:58 2022 +0200 @@ -33,6 +33,7 @@ #include "../../public/webdav.h" #include <libpq-fe.h> +#include <ucx/buffer.h> #ifdef __cplusplus extern "C" { @@ -56,6 +57,8 @@ WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb); +int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname); + /* ----------------- webdav backend functions ----------------- */ int pg_dav_propfind_init( WebdavPropfindRequest *rq,