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