src/server/plugins/postgresql/webdav.c

changeset 385
a1f4cb076d2f
parent 384
f9e9f2b3e299
child 401
d2bfd11d3f8f
equal deleted inserted replaced
210:21274e5950af 385:a1f4cb076d2f
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2022 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "webdav.h"
30 #include "vfs.h"
31 #include "config.h"
32
33 #include "../../util/util.h"
34 #include "../../util/pblock.h"
35
36 #include "../../daemon/http.h" // etag
37
38 #include <ucx/buffer.h>
39 #include <ucx/utils.h>
40 #include <libxml/tree.h>
41
42
43 static WebdavBackend pg_webdav_backend = {
44 pg_dav_propfind_init,
45 pg_dav_propfind_do,
46 pg_dav_propfind_finish,
47 pg_dav_proppatch_do,
48 pg_dav_proppatch_finish,
49 NULL, // opt_mkcol
50 NULL, // opt_mkcol_finish
51 NULL, // opt_delete
52 NULL, // opt_delete_finish
53 0,
54 NULL,
55 NULL
56 };
57
58
59
60 /*
61 * SQL query components
62 */
63
64 /*
65 * PROPFIND queries are build from components:
66 *
67 * cte cte_recursive or empty
68 * select
69 * ppath ppath column
70 * cols list of columns
71 * ext_cols* list of extension columns
72 * from from [table] / from cte
73 * prop_join join with property table, allprop or plist
74 * ext_join* extension table join
75 * where different where clauses for depth0 and depth1
76 * order depth0 doesn't need order
77 */
78
79 static const char *sql_propfind_cte_recursive = "\
80 with recursive resolvepath as (\n\
81 select\n\
82 '' as ppath,\n\
83 *\n\
84 from Resource\n\
85 where resource_id = $1 \n\
86 union\n\
87 select\n\
88 p.ppath || '/' || r.nodename,\n\
89 r.*\n\
90 from Resource r\n\
91 inner join resolvepath p on r.parent_id = p.resource_id\n\
92 )\n";
93
94 static const char *sql_propfind_select = "\
95 select\n";
96
97 static const char *sql_propfind_ppath_depth0 = "\
98 $2::text as ppath,\n";
99
100 static const char *sql_propfind_ppath_depth1 = "\
101 case when r.resource_id = $1 then $2\n\
102 else $2 || '/' || r.nodename\n\
103 end as ppath,\n";
104
105 static const char *sql_propfind_ppath_depth_infinity = "\
106 case when r.resource_id = $1 then $2\n\
107 else $2 || r.ppath\n\
108 end as ppath,\n";
109
110 static const char *sql_propfind_cols = "\
111 r.resource_id,\n\
112 r.parent_id,\n\
113 r.nodename,\n\
114 r.iscollection,\n\
115 r.lastmodified,\n\
116 r.creationdate,\n\
117 r.contentlength,\n\
118 r.etag,\n\
119 p.prefix,\n\
120 p.xmlns,\n\
121 p.pname,\n\
122 p.lang,\n\
123 p.nsdeflist,\n\
124 p.pvalue\n";
125
126 static const char *sql_propfind_from_table = "\
127 from Resource r\n";
128
129 static const char *sql_propfind_from_cte = "\
130 from resolvepath r\n";
131
132 static const char *sql_propfind_propjoin_allprop = "\
133 left join Property p on r.resource_id = p.resource_id\n";
134
135 static const char *sql_propfind_propjoin_plist = "\
136 left join (\n\
137 select p.* from Property p\
138 inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\
139 on p.xmlns = n.xmlns and p.pname = n.pname\n\
140 ) p on r.resource_id = p.resource_id\n";
141
142 static const char *sql_propfind_where_depth0 = "\
143 where r.resource_id = $1\n";
144
145 static const char *sql_propfind_where_depth1 = "\
146 where r.resource_id = $1 or r.parent_id = $1\n";
147
148 static const char *sql_propfind_order_depth1 = "\
149 order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id";
150
151 static const char *sql_propfind_order_depth_infinity = "\
152 order by replace(ppath, '/', chr(1)), resource_id";
153
154 /*
155 * SQL Queries
156 */
157
158
159 // proppatch: set property
160 // params: $1: resource_id
161 // $2: xmlns prefix
162 // $3: xmlns href
163 // $4: property name
164 // $5: lang attribute value
165 // $6: namespace list string
166 // $7: property value
167 static const char *sql_proppatch_set = "\
168 insert into Property(resource_id, prefix, xmlns, pname, lang, nsdeflist, pvalue)\n\
169 values($1, $2, $3, $4, $5, $6, $7)\n\
170 on conflict (resource_id, xmlns, pname) do\n\
171 update set prefix=$2, lang=$5, nsdeflist=$6, pvalue=$7;";
172
173 // proppatch: remove property
174 // params: $1: resource_id
175 // $2: xmlns href
176 // $3: property name
177 static const char *sql_proppatch_remove = "\
178 delete from Property where resource_id = $1 and xmlns = $2 and pname = $3";
179
180
181 void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) {
182 return pg_init_repo(cfg, pool, config);
183 }
184
185 WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData) {
186 PgRepository *repo = initData;
187
188 char *resource_pool;
189 if(repo) {
190 resource_pool = repo->resourcepool.ptr;
191 } else {
192 // resourcepool is required
193 resource_pool = pblock_findval("resourcepool", pb);
194 if(!resource_pool) {
195 log_ereport(LOG_MISCONFIG, "pg_webdav_create: missing resourcepool parameter");
196 return NULL;
197 }
198 }
199
200 // get the resource first (should only fail in case of misconfig)
201 ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0);
202 if(!resdata) {
203 log_ereport(LOG_MISCONFIG, "postgresql webdav: resource pool %s not found", resource_pool);
204 return NULL;
205 }
206
207 return pg_webdav_create_from_resdata(sn, rq, repo, resdata);
208 }
209
210 WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) {
211 WebdavBackend *webdav = pool_malloc(sn->pool, sizeof(WebdavBackend));
212 if(!webdav) {
213 return NULL;
214 }
215 *webdav = pg_webdav_backend;
216
217 PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend));
218 if(!instance) {
219 pool_free(sn->pool, webdav);
220 return NULL;
221 }
222 webdav->instance = instance;
223
224 instance->pg_resource = resdata;
225 instance->connection = resdata->data;
226
227 instance->repository = repo;
228 snprintf(instance->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id);
229
230 return webdav;
231 }
232
233 WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) {
234 return NULL;
235 }
236
237 /*
238 * adds str to the buffer
239 * some characters will be escaped: \,{}
240 */
241 static void buf_addstr_escaped(UcxBuffer *buf, const char *str) {
242 size_t len = strlen(str);
243 for(size_t i=0;i<len;i++) {
244 char c = str[i];
245 if(c == '{' || c == '}' || c == ',' || c == '\\') {
246 ucx_buffer_putc(buf, '\\');
247 }
248 ucx_buffer_putc(buf, c);
249 }
250 }
251
252 /*
253 * convert a property list to two pg array parameter strings
254 * array format: {elm1,elm2,elm3}
255 * xmlns: buffer for the xmlns array
256 * pname: buffer for the property name array
257 *
258 * returns 0 on success, 1 otherwise
259 */
260 int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname) {
261 ucx_buffer_putc(xmlns, '{');
262 ucx_buffer_putc(pname, '{');
263 while(plist) {
264 WebdavProperty *property = plist->property;
265 if(property && property->namespace && property->namespace->href && property->name) {
266 buf_addstr_escaped(xmlns, (const char*)property->namespace->href);
267 buf_addstr_escaped(pname, (const char*)property->name);
268 if(plist->next) {
269 ucx_buffer_putc(xmlns, ',');
270 ucx_buffer_putc(pname, ',');
271 }
272 }
273 plist = plist->next;
274 }
275 int r1 = ucx_buffer_write("}\0", 2, 1, xmlns) == 0;
276 int r2 = ucx_buffer_write("}\0", 2, 1, pname) == 0;
277 return r1+r2 != 0;
278 }
279
280
281 static int propfind_ext_cmp(const void *f1, const void *f2) {
282 const PgPropfindExtCol *e1 = f1;
283 const PgPropfindExtCol *e2 = f2;
284
285 if(e1->ext->tableindex != e2->ext->tableindex) {
286 return e1->ext->tableindex < e2->ext->tableindex ? -1 : 1;
287 }
288
289 return 0;
290 }
291
292
293 static int pg_create_propfind_query(
294 WebdavPropfindRequest *rq,
295 WSBool iscollection,
296 PgPropfindExtCol *ext,
297 size_t numext,
298 UcxBuffer *sql)
299 {
300 PgWebdavBackend *pgdav = rq->dav->instance;
301 PgRepository *repo = pgdav->repository;
302 int depth = !iscollection ? 0 : rq->depth;
303
304 /*
305 * PROPFIND queries are build from components:
306 *
307 * cte cte_recursive or empty
308 * select
309 * ppath ppath column
310 * cols list of columns
311 * ext_cols* list of extension columns
312 * from from [table] / from cte
313 * prop_join join with property table, allprop or plist
314 * ext_join* extension table join
315 * where different where clauses for depth0 and depth1
316 * order depth0 doesn't need order
317 */
318
319 // CTE
320 if(depth == -1) {
321 ucx_buffer_puts(sql, sql_propfind_cte_recursive);
322 }
323
324 // select
325 ucx_buffer_puts(sql, sql_propfind_select);
326
327 // ppath
328 switch(depth) {
329 case 0: ucx_buffer_puts(sql, sql_propfind_ppath_depth0); break;
330 case 1: ucx_buffer_puts(sql, sql_propfind_ppath_depth1); break;
331 case -1: ucx_buffer_puts(sql, sql_propfind_ppath_depth_infinity); break;
332 }
333
334 // cols
335 ucx_buffer_puts(sql, sql_propfind_cols);
336
337 // ext_cols
338 if(ext) {
339 if(rq->allprop) {
340 for(int i=0;i<repo->ntables;i++) {
341 ucx_bprintf(sql, ",x%d.*\n", i);
342 }
343 } else {
344 for(int i=0;i<numext;i++) {
345 PgPropfindExtCol e = ext[i];
346 ucx_bprintf(sql, ",x%d.%s\n", e.ext->tableindex, e.ext->column);
347 }
348 }
349 }
350
351 // from
352 ucx_buffer_puts(sql, depth == -1 ? sql_propfind_from_cte : sql_propfind_from_table);
353
354 // prop join
355 ucx_buffer_puts(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist);
356
357 // ext_join
358 if(ext) {
359 if(rq->allprop) {
360 for(int i=0;i<repo->ntables;i++) {
361 ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[i].table, i, i);
362 }
363 } else {
364 int tab = -1;
365 for(int i=0;i<numext;i++) {
366 PgPropfindExtCol e = ext[i];
367 if(e.ext->tableindex != tab) {
368 tab = e.ext->tableindex;
369 ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[tab].table, tab, tab);
370 }
371 }
372 }
373
374 }
375
376 // where
377 if(depth == 0) {
378 ucx_buffer_puts(sql, sql_propfind_where_depth0);
379 } else if(depth == 1) {
380 ucx_buffer_puts(sql, sql_propfind_where_depth1);
381 }
382
383 // order
384 if(depth == 1) {
385 ucx_buffer_puts(sql, sql_propfind_order_depth1);
386 } else if(depth == -1) {
387 ucx_buffer_puts(sql, sql_propfind_order_depth_infinity);
388 }
389
390 // end
391 ucx_buffer_puts(sql, ";\0");
392
393 return 0;
394 }
395
396 int pg_dav_propfind_init(
397 WebdavPropfindRequest *rq,
398 const char *path,
399 const char *href,
400 WebdavPList **outplist)
401 {
402 PgWebdavBackend *pgdav = rq->dav->instance;
403
404 // first, check if the resource exists
405 // if it doesn't exist, we can return immediately
406 int64_t parent_id;
407 int64_t resource_id;
408 const char *resourcename;
409 WSBool iscollection;
410 int res_errno = 0;
411 int err = pg_resolve_path(
412 pgdav->connection,
413 path,
414 pgdav->root_resource_id_str,
415 &parent_id,
416 &resource_id,
417 NULL, // OID
418 &resourcename,
419 &iscollection,
420 NULL, // stat
421 NULL, // etag
422 &res_errno);
423
424 if(err) {
425 if(res_errno == ENOENT) {
426 protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL);
427 }
428 return 1;
429 }
430
431 // store resource_id in rq->vars, maybe some other modules
432 // like to use it
433 char resource_id_str[32];
434 snprintf(resource_id_str, 32, "%" PRId64, resource_id);
435 pblock_nvinsert("resource_id",resource_id_str, rq->rq->vars);
436
437 // create a list of requsted extended properties
438 PgPropfindExtCol *ext;
439 size_t numext;
440 if(pgdav->repository->ntables == 0) {
441 // no property extensions configured
442 ext = NULL;
443 numext = 0;
444 } else {
445 numext = pgdav->repository->prop_ext->count;
446 ext = pool_calloc(rq->sn->pool, numext, sizeof(PgPropfindExtCol));
447
448 if(rq->allprop) {
449 // the map pgdav->repository->prop_ext contains all property extensions
450 // we can just convert the map to an array
451 UcxMapIterator i = ucx_map_iterator(pgdav->repository->prop_ext);
452 PgPropertyStoreExt *cfg_ext;
453 int j = 0;
454 UCX_MAP_FOREACH(key, cfg_ext, i) {
455 PgPropfindExtCol extcol;
456 extcol.ext = cfg_ext;
457 extcol.field_num = -1; // get the field_num after the PQexec
458 ext[j++] = extcol;
459 }
460 } else {
461 WebdavPListIterator i = webdav_plist_iterator(outplist);
462 WebdavPList *cur;
463 int j = 0;
464 while(webdav_plist_iterator_next(&i, &cur)) {
465 WSNamespace *ns = cur->property->namespace;
466 if(ns) {
467 UcxKey pkey = webdav_property_key((const char*)ns->href, cur->property->name);
468 PgPropertyStoreExt *cfg_ext = ucx_map_get(pgdav->repository->prop_ext, pkey);
469 free((void*)pkey.data);
470 if(cfg_ext) {
471 PgPropfindExtCol extcol;
472 extcol.ext = cfg_ext;
473 extcol.field_num = -1; // get the field_num after the PQexec
474 ext[j++] = extcol;
475
476 webdav_plist_iterator_remove_current(&i);
477 }
478 }
479 }
480 numext = j;
481 }
482
483 qsort(ext, numext, sizeof(PgPropfindExtCol), propfind_ext_cmp);
484 }
485
486 // create sql query
487 const char *query = NULL;
488 UcxBuffer *sql = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND);
489 if(pg_create_propfind_query(rq, iscollection, ext, numext, sql)) {
490 return 1;
491 }
492 query = sql->space;
493
494 // get all resources and properties
495 size_t href_len = strlen(href);
496 char *href_param = pool_malloc(rq->sn->pool, href_len + 1);
497 memcpy(href_param, href, href_len);
498 if(href_param[href_len-1] == '/') {
499 href_len--;
500 }
501 href_param[href_len] = '\0';
502
503 // if allprop is false, create array pair for xmlns/property names
504 UcxBuffer *xmlns_buf = NULL;
505 UcxBuffer *pname_buf = NULL;
506 char *xmlns_param = NULL;
507 char *pname_param = NULL;
508 int nparam = 2;
509 if(!rq->allprop) {
510 size_t bufsize = rq->propcount < 200 ? 8 + rq->propcount * 32 : 4096;
511 xmlns_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND);
512 if(!xmlns_buf) {
513 return 1;
514 }
515 pname_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND);
516 if(!pname_buf) {
517 ucx_buffer_free(xmlns_buf);
518 return 1;
519 }
520 if(pg_create_property_param_arrays(*outplist, xmlns_buf, pname_buf)) {
521 ucx_buffer_free(xmlns_buf);
522 ucx_buffer_free(pname_buf);
523 return 1;
524 }
525 xmlns_param = xmlns_buf->space;
526 pname_param = pname_buf->space;
527 nparam = 4;
528 }
529
530 const char* params[4] = { resource_id_str, href_param, xmlns_param, pname_param };
531 PGresult *result = PQexecParams(
532 pgdav->connection,
533 query,
534 nparam, // number of parameters
535 NULL,
536 params, // parameter value
537 NULL,
538 NULL,
539 0); // 0: result in text format
540 int nrows = PQntuples(result);
541 pool_free(rq->sn->pool, href_param);
542 if(xmlns_buf) {
543 ucx_buffer_free(xmlns_buf);
544 ucx_buffer_free(pname_buf);
545 }
546 if(nrows < 1) {
547 PQclear(result);
548 return 1;
549 }
550
551 PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind));
552 rq->userdata = pg;
553
554 pg->path = path;
555 pg->resource_id = resource_id;
556 pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0);
557 pg->result = result;
558 pg->nrows = nrows;
559
560 pg->ext = ext;
561 pg->numext = numext;
562 if(ext) {
563 // get field_nums for all property extensions
564 for(int i=0;i<numext;i++) {
565 PgPropfindExtCol *c = &ext[i];
566 c->field_num = PQfnumber(result, c->ext->column);
567 }
568 }
569
570 return 0;
571 }
572
573 int pg_dav_propfind_do(
574 WebdavPropfindRequest *rq,
575 WebdavResponse *response,
576 VFS_DIR parent,
577 WebdavResource *resource,
578 struct stat *s)
579 {
580 PgPropfind *pg = rq->userdata;
581 pool_handle_t *pool = rq->sn->pool;
582 PGresult *result = pg->result;
583 WebdavVFSProperties vfsprops = pg->vfsproperties;
584
585 WSBool vfsprops_set = 0; // are live properties added to the response?
586 WSBool extprops_set = 0; // are extended properties added to the response?
587 int64_t current_resource_id = pg->resource_id;
588 for(int r=0;r<pg->nrows;r++) {
589 // columns:
590 // 0: path
591 // 1: resource_id
592 // 2: parent_id
593 // 3: nodename
594 // 4: iscollection
595 // 5: lastmodified
596 // 6: creationdate
597 // 7: contentlength
598 // 8: etag
599 // 9: property prefix
600 // 10: property xmlns
601 // 11: property name
602 // 12: property lang
603 // 13: property nsdeflist
604 // 14: property value
605
606 char *path = PQgetvalue(result, r, 0);
607 char *res_id = PQgetvalue(result, r, 1);
608 char *iscollection_str = PQgetvalue(result, r, 4);
609 WSBool iscollection = iscollection_str && iscollection_str[0] == 't';
610 int64_t resource_id;
611 if(!util_strtoint(res_id, &resource_id)) {
612 log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id);
613 return 1;
614 }
615
616 if(resource_id != current_resource_id) {
617 // create a href string for the new resource
618 // if the resource is a collection, it should have a trailing '/'
619 size_t pathlen = strlen(path);
620 if(pathlen == 0) {
621 log_ereport(LOG_FAILURE, "pg_dav_propfind_do: query returned invalid path");
622 return 1;
623 }
624 if(pathlen > PG_MAX_PATH_LEN) {
625 log_ereport(LOG_FAILURE, "pg_dav_propfind_do: path too long: resource_id: %s", res_id);
626 return 1;
627 }
628 char *newres_href = pool_malloc(pool, (pathlen*3)+2);
629 util_uri_escape(newres_href, path);
630 if(iscollection && path[pathlen-1] != '/') {
631 size_t newres_href_len = strlen(newres_href);
632 newres_href[newres_href_len] = '/';
633 newres_href[newres_href_len+1] = '\0';
634 }
635
636 // new resource
637 resource = response->addresource(response, newres_href);
638 vfsprops_set = FALSE;
639 extprops_set = FALSE;
640 current_resource_id = resource_id;
641 }
642
643 // standard webdav live properties
644 if(!vfsprops_set) {
645 if(vfsprops.getresourcetype) {
646 if(iscollection) {
647 resource->addproperty(resource, webdav_resourcetype_collection(), 200);
648 } else {
649 resource->addproperty(resource, webdav_resourcetype_empty(), 200);
650 }
651 }
652
653 char *lastmodified = PQgetvalue(result, r, 5);
654 char *contentlength = PQgetvalue(result, r, 7);
655 time_t t = pg_convert_timestamp(lastmodified);
656
657 if(vfsprops.getlastmodified) {
658 struct tm tm;
659 gmtime_r(&t, &tm);
660
661 char buf[HTTP_DATE_LEN+1];
662 strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, &tm);
663 webdav_resource_add_dav_stringproperty(resource, pool, "getlastmodified", buf, strlen(buf));
664 }
665 if(vfsprops.creationdate) {
666 char *creationdate = PQgetvalue(result, r, 6);
667 webdav_resource_add_dav_stringproperty(resource, pool, "creationdate", creationdate, strlen(creationdate));
668 }
669 if(vfsprops.getcontentlength && !iscollection) {
670 webdav_resource_add_dav_stringproperty(resource, pool, "getcontentlength", contentlength, strlen(contentlength));
671 }
672 if(vfsprops.getetag) {
673 char *etag = PQgetvalue(result, r, 8);
674 if(!PQgetisnull(result, r, 8)) {
675 webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag));
676 } else {
677 int64_t ctlen;
678 if(util_strtoint(contentlength, &ctlen)) {
679 char etag[MAX_ETAG];
680 http_format_etag(rq->sn, rq->rq, etag, MAX_ETAG, ctlen, t);
681 webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag));
682 }
683 }
684 }
685
686 vfsprops_set = TRUE;
687 }
688
689 if(!extprops_set) {
690 // extended properties
691 if(pg->ext) {
692 for(int extc=0;extc<pg->numext;extc++) {
693 PgPropfindExtCol ext = pg->ext[extc];
694 int fieldnum = ext.field_num;
695
696 if(!PQgetisnull(result, r, fieldnum)) {
697 char *ext_value = PQgetvalue(result, r, fieldnum);
698 int ext_value_len = PQgetlength(result, r, fieldnum);
699 char ext_xmlns_prefix[32];
700 snprintf(ext_xmlns_prefix, 32, "x%d", ext.ext->tableindex);
701
702 WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
703 property->lang = NULL;
704 property->name = pool_strdup(pool, ext.ext->name);
705
706 xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
707 memset(namespace, 0, sizeof(struct _xmlNs));
708 namespace->href = (xmlChar*)pool_strdup(pool, ext.ext->ns);
709 namespace->prefix = (xmlChar*)pool_strdup(pool, ext_xmlns_prefix);
710 property->namespace = namespace;
711
712 char *content = pool_malloc(pool, ext_value_len+1);
713 memcpy(content, ext_value, ext_value_len);
714 content[ext_value_len] = '\0';
715
716 WebdavNSList *nslist = pool_malloc(pool, sizeof(WebdavNSList));
717 nslist->namespace = namespace;
718 nslist->prev = NULL;
719 nslist->next = NULL;
720
721 property->vtype = WS_VALUE_XML_DATA;
722 property->value.data.data = content;
723 property->value.data.length = ext_value_len;
724 property->value.data.namespaces = nslist;
725
726 resource->addproperty(resource, property, 200);
727 }
728 }
729 }
730
731 extprops_set = TRUE;
732 }
733
734 // dead properties
735 if(!PQgetisnull(result, r, 9)) {
736 char *prefix = PQgetvalue(result, r, 9);
737 char *xmlns = PQgetvalue(result, r, 10);
738 char *pname = PQgetvalue(result, r, 11);
739 char *lang = PQgetvalue(result, r, 12);
740 char *nsdef = PQgetvalue(result, r, 13);
741 char *pvalue = PQgetvalue(result, r, 14);
742
743 int pvalue_len = PQgetlength(result, r, 14);
744 WSBool lang_isnull = PQgetisnull(result, r, 12);
745 WSBool nsdef_isnull = PQgetisnull(result, r, 13);
746 WSBool pvalue_isnull = PQgetisnull(result, r, 14);
747
748 WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty));
749 property->lang = NULL;
750 property->name = pool_strdup(pool, pname);
751
752 xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs));
753 memset(namespace, 0, sizeof(struct _xmlNs));
754 namespace->href = (xmlChar*)pool_strdup(pool, xmlns);
755 namespace->prefix = (xmlChar*)pool_strdup(pool, prefix);
756 property->namespace = namespace;
757
758 if(!lang_isnull) {
759 property->lang = pool_strdup(pool, lang);
760 }
761
762 if(!pvalue_isnull) {
763 char *content = pool_malloc(pool, pvalue_len+1);
764 memcpy(content, pvalue, pvalue_len);
765 content[pvalue_len] = '\0';
766
767 if(nsdef_isnull) {
768 property->vtype = WS_VALUE_TEXT;
769 property->value.text.str = content;
770 property->value.text.length = pvalue_len;
771 } else {
772 WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef);
773 property->vtype = WS_VALUE_XML_DATA;
774 property->value.data.data = content;
775 property->value.data.length = pvalue_len;
776 property->value.data.namespaces = nslist;
777
778 }
779 }
780
781 resource->addproperty(resource, property, 200);
782 }
783 }
784
785 return 0;
786 }
787
788 int pg_dav_propfind_finish(WebdavPropfindRequest *rq) {
789 PgPropfind *pg = rq->userdata;
790 pool_handle_t *pool = rq->sn->pool;
791 PGresult *result = pg->result;
792
793 PQclear(result);
794
795 return 0;
796 }
797
798 enum PgDavProp {
799 PG_DAV_PROPPATCH_NOT_ALLOWED = 0,
800 PG_DAV_CREATIONDATE,
801 PG_DAV_DISPLAYNAME,
802 PG_DAV_DEADPROP
803 };
804 /*
805 * checks if the property can be manipulated
806 */
807 static enum PgDavProp proppatch_check_dav_prop(const char *name) {
808 if(!strcmp(name, "getlastmodified")) {
809 return PG_DAV_PROPPATCH_NOT_ALLOWED;
810 } else if(!strcmp(name, "getcontentlength")) {
811 return PG_DAV_PROPPATCH_NOT_ALLOWED;
812 } else if(!strcmp(name, "resourcetype")) {
813 return PG_DAV_PROPPATCH_NOT_ALLOWED;
814 } else if(!strcmp(name, "getetag")) {
815 return PG_DAV_PROPPATCH_NOT_ALLOWED;
816 } else if(!strcmp(name, "creationdate")) {
817 return PG_DAV_CREATIONDATE;
818 } else if(!strcmp(name, "displayname")) {
819 return PG_DAV_DISPLAYNAME;
820 }
821 return PG_DAV_DEADPROP;
822 }
823
824 typedef struct {
825 WebdavProperty *creationdate;
826 WebdavProperty *displayname;
827 int error;
828 } PgProppatchOpResult;
829
830 typedef int(*pg_proppatch_func)(PgWebdavBackend*, WebdavProppatchRequest*, WebdavResource*, WebdavProperty*, void*);
831
832 /*
833 * This function iterates the property list 'plist',
834 * analyses if any DAV: property is in the list
835 * and calls opfunc for the each property
836 *
837 * If the property list contains the properties creationdate or displayname,
838 * the pointers to these properties will be stored in the result structure
839 */
840 static PgProppatchOpResult pg_proppatch_op(
841 PgWebdavBackend *pgdav,
842 WebdavProppatchRequest *request,
843 WebdavResource *response,
844 WebdavPList **plist,
845 enum PgDavProp forbidden_extra,
846 pg_proppatch_func opfunc,
847 void *op_userdata)
848 {
849 PgProppatchOpResult result;
850 result.creationdate = NULL;
851 result.displayname = NULL;
852 result.error = 0;
853
854 WebdavPListIterator i = webdav_plist_iterator(plist);
855 WebdavPList *cur;
856 while(webdav_plist_iterator_next(&i, &cur)) {
857 WebdavProperty *property = cur->property;
858 WSNamespace *ns = property->namespace;
859 if(!ns) {
860 continue; // maybe we should abort
861 }
862
863 // check if the property is a DAV: property that requires special
864 // handling
865 // get* properties can't be manipulated
866 // some properties can't be removed
867 if(!strcmp((const char*)ns->href, "DAV:")) {
868 const char *name = property->name;
869 enum PgDavProp davprop = proppatch_check_dav_prop(name);
870 if(davprop != PG_DAV_DEADPROP) {
871 if(davprop == PG_DAV_PROPPATCH_NOT_ALLOWED || davprop == forbidden_extra) {
872 response->addproperty(response, property, 409);
873 } else if(davprop == PG_DAV_CREATIONDATE) {
874 result.creationdate = property;
875 } else if(davprop == PG_DAV_DISPLAYNAME) {
876 result.displayname = property;
877 }
878 webdav_plist_iterator_remove_current(&i);
879 continue;
880 }
881 }
882
883 // call op func (set, remove specific code)
884 if(opfunc(pgdav, request, response, property, op_userdata)) {
885 result.error = 1;
886 break;
887 }
888
889 webdav_plist_iterator_remove_current(&i);
890 }
891
892 return result;
893 }
894
895 #define PG_PROPPATCH_EXT_SET 0
896 #define PG_PROPPATCH_EXT_REMOVE 1
897
898 static int pg_proppatch_add_ext_prop(
899 pool_handle_t *pool,
900 PgWebdavBackend *pgdav,
901 PgProppatch *proppatch,
902 WebdavProperty *property,
903 int proppatch_op)
904 {
905 UcxKey pkey = webdav_property_key((const char*)property->namespace->href, property->name);
906 PgPropertyStoreExt *ext = ucx_map_get(pgdav->repository->prop_ext, pkey);
907 free((void*)pkey.data);
908 if(ext) {
909 PgProppatchExtProp *ext_prop = pool_malloc(pool, sizeof(PgProppatchExtProp));
910 if(!ext_prop) {
911 return 1;
912 }
913 ext_prop->column = ext;
914 ext_prop->property = property;
915
916 UcxAllocator a = util_pool_allocator(pool);
917 proppatch->ext[ext->tableindex].isused = TRUE;
918
919 UcxList **list = proppatch_op == PG_PROPPATCH_EXT_REMOVE
920 ? &proppatch->ext[ext->tableindex].remove
921 : &proppatch->ext[ext->tableindex].set;
922 *list = ucx_list_append_a(&a, *list, ext_prop);
923
924 proppatch->extensions_used = TRUE;
925 }
926
927 return 0;
928 }
929
930 static int pg_dav_set_property(
931 PgWebdavBackend *pgdav,
932 WebdavProppatchRequest *request,
933 WebdavResource *response,
934 WebdavProperty *property,
935 void *userdata)
936 {
937 pool_handle_t *pool = request->sn->pool;
938 PgProppatch *proppatch = request->userdata;
939 WSNamespace *ns = property->namespace;
940
941 // check if the property belongs to an extension
942 if(proppatch->ext && ns) {
943 return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_SET);
944 }
945
946 char *resource_id_str = userdata;
947 int ret = 0;
948
949 // convert the property value to WSXmlData
950 // property->vtype == WS_VALUE_XML_NODE should always be true
951 WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
952 char *value_str = NULL;
953 char *nsdef_str = NULL;
954 if(property_value) {
955 value_str = property_value->data;
956 if(property_value->namespaces) {
957 nsdef_str = wsxml_nslist2string(pool, property_value->namespaces);
958 if(!nsdef_str) {
959 return 1; // OOM
960 }
961 }
962 }
963
964 // exec sql
965 const char* params[7] = { resource_id_str, (const char*)ns->prefix, (const char*)ns->href, property->name, NULL, nsdef_str, value_str};
966 PGresult *result = PQexecParams(
967 pgdav->connection,
968 sql_proppatch_set,
969 7, // number of parameters
970 NULL,
971 params, // parameter value
972 NULL,
973 NULL,
974 0); // 0: result in text format
975
976 if(PQresultStatus(result) != PGRES_COMMAND_OK) {
977 response->addproperty(response, property, 500);
978 //printf(PQerrorMessage(pgdav->connection));
979 //fflush(stdout);
980 ret = 1;
981 } else {
982 response->addproperty(response, property, 200);
983 }
984 PQclear(result);
985 if(value_str) pool_free(pool, value_str);
986
987 return ret;
988 }
989
990
991 static int pg_dav_remove_property(
992 PgWebdavBackend *pgdav,
993 WebdavProppatchRequest *request,
994 WebdavResource *response,
995 WebdavProperty *property,
996 void *userdata)
997 {
998 pool_handle_t *pool = request->sn->pool;
999 PgProppatch *proppatch = request->userdata;
1000 WSNamespace *ns = property->namespace;
1001
1002 // check if the property belongs to an extension
1003 if(proppatch->ext && ns) {
1004 return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_REMOVE);
1005 }
1006
1007 char *resource_id_str = userdata;
1008 int ret = 0;
1009
1010 // exec sql
1011 const char* params[3] = { resource_id_str, (const char*)ns->href, property->name };
1012 PGresult *result = PQexecParams(
1013 pgdav->connection,
1014 sql_proppatch_remove,
1015 3, // number of parameters
1016 NULL,
1017 params, // parameter value
1018 NULL,
1019 NULL,
1020 0); // 0: result in text format
1021
1022 if(PQresultStatus(result) != PGRES_COMMAND_OK) {
1023 response->addproperty(response, property, 500);
1024 //printf(PQerrorMessage(pgdav->connection));
1025 //fflush(stdout);
1026 ret = 1;
1027 }
1028 PQclear(result);
1029
1030 return ret;
1031 }
1032
1033
1034 /*
1035 * Creates an SQL query for inserting a new row to an extension table
1036 * A parameter list for PQexecParams will also be generated, however
1037 * params[0] will be empty (resource_id str)
1038 *
1039 * Query: insert into <table> (resource_id, col1, ...) values ($1, $2 ...);
1040 */
1041 static UcxBuffer* ext_row_create_insert_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) {
1042 pool_handle_t *pool = request->sn->pool;
1043
1044 UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
1045 if(!sql) {
1046 return NULL;
1047 }
1048
1049 size_t pg_nparams = ucx_list_size(ext->set) + 1;
1050 char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*));
1051 if(!pg_params) {
1052 ucx_buffer_free(sql);
1053 return NULL;
1054 }
1055
1056 ucx_buffer_puts(sql, "insert into ");
1057 ucx_buffer_puts(sql, table->table);
1058 ucx_buffer_puts(sql, "(resource_id");
1059 UCX_FOREACH(elm, ext->set) {
1060 PgProppatchExtProp *prop = elm->data;
1061 ucx_bprintf(sql, ",%s", prop->column->name);
1062 }
1063
1064 ucx_buffer_puts(sql, ") values ($1\n");
1065 int i = 1;
1066 UCX_FOREACH(elm, ext->set) {
1067 PgProppatchExtProp *prop = elm->data;
1068 WebdavProperty *property = prop->property;
1069 // convert the property value to WSXmlData
1070 // property->vtype == WS_VALUE_XML_NODE should always be true
1071 WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
1072 char *value_str = NULL;
1073 //char *nsdef_str = NULL;
1074 if(property_value) {
1075 value_str = property_value->data;
1076 if(property_value->namespaces) {
1077 // currently only text data is supported
1078 pool_free(pool, params);
1079 ucx_buffer_free(sql);
1080 return NULL;
1081 }
1082 }
1083
1084 pg_params[i] = value_str;
1085 ucx_bprintf(sql, ",$%d", ++i);
1086 }
1087 ucx_buffer_puts(sql, ");");
1088
1089
1090 //printf("\n\n%.*s\n\n", (int)sql->size, sql->space);
1091 //fflush(stdout);
1092
1093 *params = pg_params;
1094 *nparams = pg_nparams;
1095
1096 return sql;
1097 }
1098
1099 /*
1100 * Creates an SQL query for updating an extension table row
1101 * A parameter list for PQexecParams will also be generated, however
1102 * params[0] will be empty (resource_id str)
1103 *
1104 * Query: update <table> set
1105 * col1 = $2,
1106 * col2 = $3,
1107 * ...
1108 * where resource_id = $1 ;
1109 */
1110 static UcxBuffer* ext_row_create_update_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) {
1111 pool_handle_t *pool = request->sn->pool;
1112
1113 UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
1114 if(!sql) {
1115 return NULL;
1116 }
1117
1118 ucx_buffer_puts(sql, "update ");
1119 ucx_buffer_puts(sql, table->table);
1120 ucx_buffer_puts(sql, " set\n");
1121
1122 size_t pg_nparams = ucx_list_size(ext->set) + 1;
1123 char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*));
1124 if(!pg_params) {
1125 ucx_buffer_free(sql);
1126 return NULL;
1127 }
1128
1129 int i = 1;
1130 UCX_FOREACH(elm, ext->set) {
1131 PgProppatchExtProp *prop = elm->data;
1132 WebdavProperty *property = prop->property;
1133 // convert the property value to WSXmlData
1134 // property->vtype == WS_VALUE_XML_NODE should always be true
1135 WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL;
1136 char *value_str = NULL;
1137 //char *nsdef_str = NULL;
1138 if(property_value) {
1139 value_str = property_value->data;
1140 if(property_value->namespaces) {
1141 // currently only text data is supported
1142 pool_free(pool, params);
1143 ucx_buffer_free(sql);
1144 return NULL;
1145 }
1146 }
1147
1148 pg_params[i] = value_str;
1149 ucx_bprintf(sql, " %s = $%d,\n", prop->column->name, ++i);
1150 }
1151
1152 UCX_FOREACH(elm, ext->remove) {
1153 PgProppatchExtProp *prop = elm->data;
1154 ucx_bprintf(sql, " %s = NULL,\n", prop->column->name);
1155 }
1156
1157 // check if any write worked
1158 if(sql->pos == 0) {
1159 ucx_buffer_free(sql);
1160 pool_free(pool, pg_params);
1161 return NULL;
1162 }
1163
1164 // last line should end with ',' '\n'
1165 // replace ',' with space
1166 if(sql->space[sql->pos-2] == ',') {
1167 sql->space[sql->pos-2] = ' ';
1168 }
1169
1170 ucx_bprintf(sql, "where resource_id = $1 ;\0");
1171
1172 //printf("\n\n%.*s\n\n", (int)sql->size, sql->space);
1173 //fflush(stdout);
1174
1175 *params = pg_params;
1176 *nparams = pg_nparams;
1177
1178 return sql;
1179 }
1180
1181 /*
1182 * Executes an SQL insert for the extension table
1183 */
1184 int ext_row_insert(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
1185 PgWebdavBackend *pgdav = request->dav->instance;
1186 PgProppatch *proppatch = request->userdata;
1187 pool_handle_t *pool = request->sn->pool;
1188
1189 char **params;
1190 size_t nparam;
1191 UcxBuffer *sql = ext_row_create_insert_query(request, ext, table, &params, &nparam);
1192 if(!sql) {
1193 return 1;
1194 }
1195
1196 char resource_id_str[32];
1197 snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id);
1198 params[0] = resource_id_str;
1199
1200 PGresult *result = PQexecParams(
1201 pgdav->connection,
1202 sql->space,
1203 nparam, // number of parameters
1204 NULL,
1205 ( const char *const *)params, // parameter value
1206 NULL,
1207 NULL,
1208 0); // 0: result in text format
1209
1210 ucx_buffer_free(sql);
1211
1212 int ret = 1;
1213 if(PQresultStatus(result) == PGRES_COMMAND_OK) {
1214 // command ok, check if any row was updated
1215 char *nrows_affected = PQcmdTuples(result);
1216 if(nrows_affected[0] == '1') {
1217 ret = 0;
1218 } else {
1219 log_ereport(LOG_FAILURE, "pg: extension row insert failed");
1220 }
1221 } else {
1222 log_ereport(LOG_FAILURE, "pg: extension row insert failed: %s", PQresultErrorMessage(result));
1223 }
1224
1225 PQclear(result);
1226
1227 return ret;
1228 }
1229
1230 /*
1231 * Executes an SQL update for the extension table
1232 */
1233 int ext_row_update(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
1234 PgWebdavBackend *pgdav = request->dav->instance;
1235 PgProppatch *proppatch = request->userdata;
1236 pool_handle_t *pool = request->sn->pool;
1237
1238 char **params;
1239 size_t nparam;
1240 UcxBuffer *sql = ext_row_create_update_query(request, ext, table, &params, &nparam);
1241 if(!sql) {
1242 return 1;
1243 }
1244
1245 char resource_id_str[32];
1246 snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id);
1247 params[0] = resource_id_str;
1248
1249 PGresult *result = PQexecParams(
1250 pgdav->connection,
1251 sql->space,
1252 nparam, // number of parameters
1253 NULL,
1254 ( const char *const *)params, // parameter value
1255 NULL,
1256 NULL,
1257 0); // 0: result in text format
1258
1259 ucx_buffer_free(sql);
1260
1261 int ret = 1;
1262 if(PQresultStatus(result) == PGRES_COMMAND_OK) {
1263 // command ok, check if any row was updated
1264 char *nrows_affected = PQcmdTuples(result);
1265 if(nrows_affected[0] == '1') {
1266 ret = 0;
1267 } else if(nrows_affected[0] == '0') {
1268 // no rows affected, that means we have to insert a new row
1269 // in the extension table for this resource
1270
1271 // TODO: cleanup params
1272
1273 ret = ext_row_insert(request, ext, table);
1274 }
1275 } else {
1276 log_ereport(LOG_FAILURE, "pg: extension row update failed: %s", PQresultErrorMessage(result));
1277 }
1278
1279
1280 PQclear(result);
1281
1282 return ret;
1283 }
1284
1285 static int pg_dav_update_extension_tables(WebdavProppatchRequest *request) {
1286 PgWebdavBackend *pgdav = request->dav->instance;
1287 PgProppatch *proppatch = request->userdata;
1288
1289 for(int i=0;i<proppatch->numext;i++) {
1290 if(proppatch->ext[i].isused) {
1291 if(ext_row_update(request, &proppatch->ext[i], &pgdav->repository->tables[i])) {
1292 // extension proppatch failed
1293 return 1;
1294 }
1295 }
1296 }
1297
1298 return 0;
1299 }
1300
1301 int pg_dav_proppatch_do(
1302 WebdavProppatchRequest *request,
1303 WebdavResource *response,
1304 VFSFile *file,
1305 WebdavPList **out_set,
1306 WebdavPList **out_remove)
1307 {
1308 PgWebdavBackend *pgdav = request->dav->instance;
1309 pool_handle_t *pool = request->sn->pool;
1310 char *path = pblock_findkeyval(pb_key_path, request->rq->vars);
1311
1312 PgProppatch proppatch;
1313 proppatch.extensions_used = FALSE;
1314 if(pgdav->repository->ntables == 0) {
1315 proppatch.ext = NULL;
1316 proppatch.numext = 0;
1317 } else {
1318 // some properties are stored in additional tables
1319 // for each table we create a PgProppatchExt record
1320 // which stores data about, which tables are used
1321 // and which properties (columns) should be updated
1322 //
1323 // proppatch.ext[i] should contain the data for repository->tables[i]
1324 proppatch.numext = pgdav->repository->ntables;
1325 proppatch.ext = pool_calloc(request->sn->pool, proppatch.numext, sizeof(PgProppatchExt));
1326 if(!proppatch.ext) {
1327 return 1; // OOM
1328 }
1329 }
1330 request->userdata = &proppatch;
1331
1332 // check if the resource exists, we also need the resource_id
1333 int64_t parent_id;
1334 int64_t resource_id;
1335 const char *resourcename;
1336 WSBool iscollection;
1337 int res_errno = 0;
1338 int err = pg_resolve_path(
1339 pgdav->connection,
1340 path,
1341 pgdav->root_resource_id_str,
1342 &parent_id,
1343 &resource_id,
1344 NULL, // OID
1345 &resourcename,
1346 &iscollection,
1347 NULL, // stat
1348 NULL, // etag
1349 &res_errno);
1350
1351 if(err) {
1352 return 1;
1353 }
1354
1355 proppatch.resource_id = resource_id;
1356
1357 // because proppatch must be atomic and we have multiple sql
1358 // queries and other backends that do stuff that could fail
1359 // we need the possibility to reverse all changes
1360 // we use a transaction savepoint for this
1361 PGresult *result = PQexec(pgdav->connection, "savepoint proppatch;");
1362 ExecStatusType execStatus = PQresultStatus(result);
1363 PQclear(result);
1364 if(execStatus != PGRES_COMMAND_OK) {
1365 return 1;
1366 }
1367
1368 char resource_id_str[32];
1369 snprintf(resource_id_str, 32, "%" PRId64, resource_id);
1370 // store the resource_id in rq->vars, because it could be useful later
1371 pblock_nvinsert("resource_id", resource_id_str, request->rq->vars);
1372
1373 int ret = 0;
1374 PgProppatchOpResult set_res = pg_proppatch_op(
1375 pgdav,
1376 request,
1377 response,
1378 out_set,
1379 PG_DAV_PROPPATCH_NOT_ALLOWED,
1380 pg_dav_set_property,
1381 resource_id_str);
1382 if(set_res.error) {
1383 return 1;
1384 }
1385 PgProppatchOpResult rm_res = pg_proppatch_op(
1386 pgdav,
1387 request,
1388 response,
1389 out_remove,
1390 PG_DAV_CREATIONDATE, // creationdate can't be removed
1391 pg_dav_remove_property,
1392 resource_id_str);
1393 if(rm_res.error) {
1394 return 1;
1395 }
1396
1397 // if extensions are in use and pg_proppatch_op found any
1398 // properties, that should be stored in extension tables
1399 // we do the update/insert now
1400 if(proppatch.extensions_used) {
1401 ret = pg_dav_update_extension_tables(request);
1402 }
1403
1404
1405 return ret;
1406 }
1407
1408 int pg_dav_proppatch_finish(
1409 WebdavProppatchRequest *request,
1410 WebdavResource *response,
1411 VFSFile *file,
1412 WSBool commit)
1413 {
1414 PgWebdavBackend *pgdav = request->dav->instance;
1415 int ret = 0;
1416 if(!commit) {
1417 log_ereport(LOG_VERBOSE, "proppatch: rollback");
1418 PGresult *result = PQexec(pgdav->connection, "rollback to savepoint proppatch;");
1419 if(PQresultStatus(result) != PGRES_COMMAND_OK) {
1420 log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: rollback failed: %s", PQerrorMessage(pgdav->connection));
1421 ret = 1;
1422 }
1423 PQclear(result);
1424 }
1425 return ret;
1426 }

mercurial