|
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, ¶ms, &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, ¶ms, &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 } |