UNIXworkcode

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 <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, // opt_mkcol 51 NULL, // opt_mkcol_finish 52 NULL, // opt_delete 53 NULL, // opt_delete_finish 54 0, // settings 55 NULL, 56 NULL 57 }; 58 59 60 61 /* 62 * SQL query components 63 */ 64 65 /* 66 * PROPFIND queries are build from components: 67 * 68 * cte cte_recursive or empty 69 * select 70 * ppath ppath column 71 * cols list of columns 72 * ext_cols* list of extension columns 73 * from from [table] / from cte 74 * prop_join join with property table, allprop or plist 75 * ext_join* extension table join 76 * where different where clauses for depth0 and depth1 77 * order depth0 doesn't need order 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 * SQL Queries 157 */ 158 159 160 // proppatch: set property 161 // params: $1: resource_id 162 // $2: xmlns prefix 163 // $3: xmlns href 164 // $4: property name 165 // $5: lang attribute value 166 // $6: namespace list string 167 // $7: property value 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 // proppatch: remove property 175 // params: $1: resource_id 176 // $2: xmlns href 177 // $3: property name 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 // resourcepool is required 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 // get the resource first (should only fail in case of misconfig) 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 * adds str to the buffer 240 * some characters will be escaped: \,{} 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 * convert a property list to two pg array parameter strings 255 * array format: {elm1,elm2,elm3} 256 * xmlns: buffer for the xmlns array 257 * pname: buffer for the property name array 258 * 259 * returns 0 on success, 1 otherwise 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 * PROPFIND queries are build from components: 307 * 308 * cte cte_recursive or empty 309 * select 310 * ppath ppath column 311 * cols list of columns 312 * ext_cols* list of extension columns 313 * from from [table] / from cte 314 * prop_join join with property table, allprop or plist 315 * ext_join* extension table join 316 * where different where clauses for depth0 and depth1 317 * order depth0 doesn't need order 318 */ 319 320 // CTE 321 if(depth == -1) { 322 cxBufferPutString(sql, sql_propfind_cte_recursive); 323 } 324 325 // select 326 cxBufferPutString(sql, sql_propfind_select); 327 328 // ppath 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 // cols 336 cxBufferPutString(sql, sql_propfind_cols); 337 338 // ext_cols 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 // from 353 cxBufferPutString(sql, depth == -1 ? sql_propfind_from_cte : sql_propfind_from_table); 354 355 // prop join 356 cxBufferPutString(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist); 357 358 // ext_join 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 // where 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 // order 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 // end 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 // first, check if the resource exists 407 // if it doesn't exist, we can return immediately 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, // OID 420 &resourcename, 421 &iscollection, 422 NULL, // stat 423 NULL, // etag 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 // store resource_id in rq->vars, maybe some other modules 434 // like to use it 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 // create a list of requsted extended properties 440 PgPropfindExtCol *ext; 441 size_t numext; 442 if(pgdav->repository->ntables == 0) { 443 // no property extensions configured 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 // the map pgdav->repository->prop_ext contains all property extensions 452 // we can just convert the map to an array 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; // get the field_num after the PQexec 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; // get the field_num after the PQexec 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 // create sql query 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 // get all resources and properties 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 // if allprop is false, create array pair for xmlns/property names 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, // number of parameters 549 NULL, 550 params, // parameter value 551 NULL, 552 NULL, 553 0); // 0: result in text format 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 // we resolved the path, so the resource exists and nrows should 563 // be >= 1 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 // build a map of all ext field names (excluding first fields from 584 // the resource and property table) 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 // start with index 15 (see pg_dav_propfind_do for first column nums) 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 // get field_nums for all property extensions 603 for(int i=0;i<numext;i++) { 604 PgPropfindExtCol *c = &ext[i]; 605 //c->field_num = PQfnumber(result, c->ext->column); 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; // are live properties added to the response? 629 WSBool extprops_set = 0; // are extended properties added to the response? 630 int64_t current_resource_id = pg->resource_id; 631 for(int r=0;r<pg->nrows;r++) { 632 // columns: 633 // 0: path 634 // 1: resource_id 635 // 2: parent_id 636 // 3: nodename 637 // 4: iscollection 638 // 5: lastmodified 639 // 6: creationdate 640 // 7: contentlength 641 // 8: etag 642 // 9: property prefix 643 // 10: property xmlns 644 // 11: property name 645 // 12: property lang 646 // 13: property nsdeflist 647 // 14: property value 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 // create a href string for the new resource 661 // if the resource is a collection, it should have a trailing '/' 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 // new resource 680 resource = response->addresource(response, newres_href); 681 vfsprops_set = FALSE; 682 extprops_set = FALSE; 683 current_resource_id = resource_id; 684 } 685 686 // standard webdav live properties 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 // extended properties 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 // dead properties 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 * checks if the property can be manipulated 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 * This function iterates the property list 'plist', 877 * analyses if any DAV: property is in the list 878 * and calls opfunc for the each property 879 * 880 * If the property list contains the properties creationdate or displayname, 881 * the pointers to these properties will be stored in the result structure 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; // maybe we should abort 904 } 905 906 // check if the property is a DAV: property that requires special 907 // handling 908 // get* properties can't be manipulated 909 // some properties can't be removed 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 // call op func (set, remove specific code) 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 // check if the property belongs to an extension 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 } // else: property is not stored in an extension table, continue with normal property store 1005 } 1006 1007 char *resource_id_str = userdata; 1008 int ret = 0; 1009 1010 // convert the property value to WSXmlData 1011 // property->vtype == WS_VALUE_XML_NODE should always be true 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; // OOM 1021 } 1022 } 1023 } 1024 1025 // exec sql 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, // number of parameters 1031 NULL, 1032 params, // parameter value 1033 NULL, 1034 NULL, 1035 0); // 0: result in text format 1036 1037 if(PQresultStatus(result) != PGRES_COMMAND_OK) { 1038 response->addproperty(response, property, 500); 1039 //printf(PQerrorMessage(pgdav->connection)); 1040 //fflush(stdout); 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 // check if the property belongs to an extension 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 } // else: property is not stored in an extension table, continue with normal property store 1069 } 1070 1071 char *resource_id_str = userdata; 1072 int ret = 0; 1073 1074 // exec sql 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, // number of parameters 1080 NULL, 1081 params, // parameter value 1082 NULL, 1083 NULL, 1084 0); // 0: result in text format 1085 1086 if(PQresultStatus(result) != PGRES_COMMAND_OK) { 1087 response->addproperty(response, property, 500); 1088 //printf(PQerrorMessage(pgdav->connection)); 1089 //fflush(stdout); 1090 ret = 1; 1091 } 1092 PQclear(result); 1093 1094 return ret; 1095 } 1096 1097 1098 /* 1099 * Creates an SQL query for inserting a new row to an extension table 1100 * A parameter list for PQexecParams will also be generated, however 1101 * params[0] will be empty (resource_id str) 1102 * 1103 * Query: insert into <table> (resource_id, col1, ...) values ($1, $2 ...); 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 // convert the property value to WSXmlData 1137 // property->vtype == WS_VALUE_XML_NODE should always be true 1138 WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; 1139 char *value_str = NULL; 1140 //char *nsdef_str = NULL; 1141 if(property_value) { 1142 value_str = property_value->data; 1143 if(property_value->namespaces) { 1144 // currently only text data is supported 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 //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); 1158 //fflush(stdout); 1159 1160 *params = pg_params; 1161 *nparams = pg_nparams; 1162 1163 return sql; 1164 } 1165 1166 /* 1167 * Creates an SQL query for updating an extension table row 1168 * A parameter list for PQexecParams will also be generated, however 1169 * params[0] will be empty (resource_id str) 1170 * 1171 * Query: update <table> set 1172 * col1 = $2, 1173 * col2 = $3, 1174 * ... 1175 * where resource_id = $1 ; 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 // convert the property value to WSXmlData 1205 // property->vtype == WS_VALUE_XML_NODE should always be true 1206 WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; 1207 char *value_str = NULL; 1208 //char *nsdef_str = NULL; 1209 if(property_value) { 1210 value_str = property_value->data; 1211 if(property_value->namespaces) { 1212 // currently only text data is supported 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 // check if any write worked 1228 if(sql->pos == 0) { 1229 cxBufferDestroy(sql); 1230 pool_free(pool, pg_params); 1231 return NULL; 1232 } 1233 1234 // last line should end with ',' '\n' 1235 // replace ',' with space 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 //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); 1244 //fflush(stdout); 1245 1246 *params = pg_params; 1247 *nparams = pg_nparams; 1248 1249 return sql; 1250 } 1251 1252 /* 1253 * Executes an SQL insert for the extension table 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, &params, &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, // number of parameters 1275 NULL, 1276 ( const char *const *)params, // parameter value 1277 NULL, 1278 NULL, 1279 0); // 0: result in text format 1280 1281 cxBufferDestroy(sql); 1282 1283 int ret = 1; 1284 if(PQresultStatus(result) == PGRES_COMMAND_OK) { 1285 // command ok, check if any row was updated 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 * Executes an SQL update for the extension table 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, &params, &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, // number of parameters 1324 NULL, 1325 ( const char *const *)params, // parameter value 1326 NULL, 1327 NULL, 1328 0); // 0: result in text format 1329 1330 cxBufferDestroy(sql); 1331 1332 int ret = 1; 1333 if(PQresultStatus(result) == PGRES_COMMAND_OK) { 1334 // command ok, check if any row was updated 1335 char *nrows_affected = PQcmdTuples(result); 1336 if(nrows_affected[0] == '1') { 1337 ret = 0; 1338 } else if(nrows_affected[0] == '0') { 1339 // no rows affected, that means we have to insert a new row 1340 // in the extension table for this resource 1341 1342 // TODO: cleanup params 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 // extension proppatch failed 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 // some properties are stored in additional tables 1390 // for each table we create a PgProppatchExt record 1391 // which stores data about, which tables are used 1392 // and which properties (columns) should be updated 1393 // 1394 // proppatch.ext[i] should contain the data for repository->tables[i] 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; // OOM 1399 } 1400 } 1401 request->userdata = &proppatch; 1402 1403 // check if the resource exists, we also need the resource_id 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, // OID 1416 &resourcename, 1417 &iscollection, 1418 NULL, // stat 1419 NULL, // etag 1420 &res_errno); 1421 1422 if(err) { 1423 return 1; 1424 } 1425 1426 proppatch.resource_id = resource_id; 1427 1428 // because proppatch must be atomic and we have multiple sql 1429 // queries and other backends that do stuff that could fail 1430 // we need the possibility to reverse all changes 1431 // we use a transaction savepoint for this 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 // store the resource_id in rq->vars, because it could be useful later 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, // creationdate can't be removed 1462 pg_dav_remove_property, 1463 resource_id_str); 1464 if(rm_res.error) { 1465 return 1; 1466 } 1467 1468 // if extensions are in use and pg_proppatch_op found any 1469 // properties, that should be stored in extension tables 1470 // we do the update/insert now 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