Wed, 10 Aug 2022 20:32:49 +0200
implement named dav repositories
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2022 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "webdav.h" #include "vfs.h" #include "config.h" #include "../../util/util.h" #include "../../util/pblock.h" #include "../../daemon/http.h" // etag #include <ucx/buffer.h> #include <libxml/tree.h> static WebdavBackend pg_webdav_backend = { pg_dav_propfind_init, pg_dav_propfind_do, pg_dav_propfind_finish, pg_dav_proppatch_do, pg_dav_proppatch_finish, NULL, // opt_mkcol NULL, // opt_mkcol_finish NULL, // opt_delete NULL, // opt_delete_finish 0, NULL, NULL }; /* * SQL Queries */ // propfind with depth = 0 // params: $1: resource_id static const char *sql_propfind_allprop_depth0 = "\ select\n\ $2::text as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from Resource r\n\ left join Property p on r.resource_id = p.resource_id\n\ where r.resource_id = $1;"; // propfind with depth = 0 for specific properties // params: $1: resource_id static const char *sql_propfind_depth0 = "\ select\n\ $2::text as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from Resource r\n\ left join (\n\ select p.* from Property p\ inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ on p.xmlns = n.xmlns and p.pname = n.pname\n\ ) p on r.resource_id = p.resource_id\n\ where r.resource_id = $1;"; // propfind with depth = 1 // params: $1: resource_id static const char *sql_propfind_allprop_depth1 = "\ select\n\ case when r.resource_id = $1 then $2\n\ else $2 || '/' || r.nodename\n\ end as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from Resource r\n\ left join Property p on r.resource_id = p.resource_id\n\ where r.resource_id = $1 or r.parent_id = $1\n\ order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id;"; // propfind with depth = 1 for specific properties // params: $1: resource_id static const char *sql_propfind_depth1 = "\ select\n\ case when r.resource_id = $1 then $2\n\ else $2 || '/' || r.nodename\n\ end as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from Resource r\n\ left join (\n\ select p.* from Property p\ inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ on p.xmlns = n.xmlns and p.pname = n.pname\n\ ) p on r.resource_id = p.resource_id\n\ where r.resource_id = $1 or r.parent_id = $1\n\ order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id;"; // recursive propfind // params: $1: resource_id static const char *sql_propfind_allprop_recursive = "\ with recursive resolvepath as (\n\ select\n\ '' as ppath,\n\ *\n\ from Resource\n\ where resource_id = $1 \n\ union\n\ select\n\ p.ppath || '/' || r.nodename,\n\ r.*\n\ from Resource r\n\ inner join resolvepath p on r.parent_id = p.resource_id\n\ )\n\ select\n\ case when r.resource_id = $1 then $2\n\ else $2 || r.ppath\n\ end as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from resolvepath r\n\ left join Property p on r.resource_id = p.resource_id\n\ order by replace(ppath, '/', chr(1)), resource_id;"; // recursive propfind for specific properties // params: $1: resource_id // $2: array of property xmlns // $3: array of property names static const char *sql_propfind_recursive = "\ with recursive resolvepath as (\n\ select\n\ '' as ppath,\n\ *\n\ from Resource\n\ where resource_id = $1 \n\ union\n\ select\n\ p.ppath || '/' || r.nodename,\n\ r.*\n\ from Resource r\n\ inner join resolvepath p on r.parent_id = p.resource_id\n\ )\n\ select\n\ case when r.resource_id = $1 then $2\n\ else $2 || r.ppath\n\ end as ppath,\n\ r.resource_id,\n\ r.parent_id,\n\ r.nodename,\n\ r.iscollection,\n\ r.lastmodified,\n\ r.creationdate,\n\ r.contentlength,\n\ r.etag,\n\ p.prefix,\n\ p.xmlns,\n\ p.pname,\n\ p.lang,\n\ p.nsdeflist,\n\ p.pvalue\n\ from resolvepath r\n\ left join (\n\ select p.* from Property p\ inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ on p.xmlns = n.xmlns and p.pname = n.pname\n\ ) p on r.resource_id = p.resource_id\n\ order by replace(ppath, '/', chr(1)), resource_id;"; // proppatch: set property // params: $1: resource_id // $2: xmlns prefix // $3: xmlns href // $4: property name // $5: lang attribute value // $6: namespace list string // $7: property value static const char *sql_proppatch_set = "\ insert into Property(resource_id, prefix, xmlns, pname, lang, nsdeflist, pvalue)\n\ values($1, $2, $3, $4, $5, $6, $7)\n\ on conflict (resource_id, xmlns, pname) do\n\ update set prefix=$2, lang=$5, nsdeflist=$6, pvalue=$7;"; // proppatch: remove property // params: $1: resource_id // $2: xmlns href // $3: property name static const char *sql_proppatch_remove = "\ delete from Property where resource_id = $1 and xmlns = $2 and pname = $3"; void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { return pg_init_repo(pool, config); } WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData) { // resourcepool is required char *resource_pool = pblock_findval("resourcepool", pb); if(!resource_pool) { log_ereport(LOG_MISCONFIG, "pg_webdav_create: missing resourcepool parameter"); return NULL; } // get the resource first (should only fail in case of misconfig) ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0); if(!resdata) { log_ereport(LOG_MISCONFIG, "postgresql webdav: resource pool %s not found", resource_pool); return NULL; } return pg_webdav_create_from_resdata(sn, rq, resdata); } WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, ResourceData *resdata) { WebdavBackend *webdav = pool_malloc(sn->pool, sizeof(WebdavBackend)); if(!webdav) { return NULL; } *webdav = pg_webdav_backend; PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend)); if(!instance) { pool_free(sn->pool, webdav); return NULL; } webdav->instance = instance; instance->pg_resource = resdata; instance->connection = resdata->data; return webdav; } WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) { return NULL; } /* * adds str to the buffer * some characters will be escaped: \,{} */ static void buf_addstr_escaped(UcxBuffer *buf, const char *str) { size_t len = strlen(str); for(size_t i=0;i<len;i++) { char c = str[i]; if(c == '{' || c == '}' || c == ',' || c == '\\') { ucx_buffer_putc(buf, '\\'); } ucx_buffer_putc(buf, c); } } /* * convert a property list to two pg array parameter strings * array format: {elm1,elm2,elm3} * xmlns: buffer for the xmlns array * pname: buffer for the property name array * * returns 0 on success, 1 otherwise */ int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname) { ucx_buffer_putc(xmlns, '{'); ucx_buffer_putc(pname, '{'); while(plist) { WebdavProperty *property = plist->property; if(property && property->namespace && property->namespace->href && property->name) { buf_addstr_escaped(xmlns, (const char*)property->namespace->href); buf_addstr_escaped(pname, (const char*)property->name); if(plist->next) { ucx_buffer_putc(xmlns, ','); ucx_buffer_putc(pname, ','); } } plist = plist->next; } int r1 = ucx_buffer_write("}\0", 2, 1, xmlns) == 0; int r2 = ucx_buffer_write("}\0", 2, 1, pname) == 0; return r1+r2 != 0; } int pg_dav_propfind_init( WebdavPropfindRequest *rq, const char *path, const char *href, WebdavPList **outplist) { PgWebdavBackend *pgdav = rq->dav->instance; // check if the resource exists int64_t parent_id; int64_t resource_id; const char *resourcename; WSBool iscollection; int res_errno = 0; int err = pg_resolve_path( pgdav->connection, path, &parent_id, &resource_id, NULL, // OID &resourcename, &iscollection, NULL, // stat NULL, // etag &res_errno); if(err) { if(res_errno == ENOENT) { protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL); } return 1; } // choose sql query const char *query = NULL; if(!iscollection || rq->depth == 0) { query = rq->allprop ? sql_propfind_allprop_depth0 : sql_propfind_depth0; } else if(rq->depth == 1) { query = rq->allprop ? sql_propfind_allprop_depth1 : sql_propfind_depth1; } else if(rq->depth == -1) { query = rq->allprop ? sql_propfind_allprop_recursive : sql_propfind_recursive; } else { log_ereport(LOG_FAILURE, "%s", "pg_dav_propfind_init: invalid depth"); return 1; } // get all resources and properties char resource_id_str[32]; snprintf(resource_id_str, 32, "%" PRId64, resource_id); size_t href_len = strlen(href); char *href_param = pool_malloc(rq->sn->pool, href_len + 1); memcpy(href_param, href, href_len); if(href_param[href_len-1] == '/') { href_len--; } href_param[href_len] = '\0'; // if allprop is false, create array pair for xmlns/property names UcxBuffer *xmlns_buf = NULL; UcxBuffer *pname_buf = NULL; char *xmlns_param = NULL; char *pname_param = NULL; int nparam = 2; if(!rq->allprop) { size_t bufsize = rq->propcount < 200 ? 8 + rq->propcount * 32 : 4096; xmlns_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); if(!xmlns_buf) { return 1; } pname_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); if(!pname_buf) { ucx_buffer_free(xmlns_buf); return 1; } if(pg_create_property_param_arrays(rq->properties, xmlns_buf, pname_buf)) { ucx_buffer_free(xmlns_buf); ucx_buffer_free(pname_buf); return 1; } xmlns_param = xmlns_buf->space; pname_param = pname_buf->space; nparam = 4; } const char* params[4] = { resource_id_str, href_param, xmlns_param, pname_param }; PGresult *result = PQexecParams( pgdav->connection, query, nparam, // number of parameters NULL, params, // parameter value NULL, NULL, 0); // 0: result in text format int nrows = PQntuples(result); pool_free(rq->sn->pool, href_param); if(xmlns_buf) { ucx_buffer_free(xmlns_buf); ucx_buffer_free(pname_buf); } if(nrows < 1) { PQclear(result); return 1; } PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind)); rq->userdata = pg; pg->path = path; pg->resource_id = resource_id; pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0); pg->result = result; pg->nrows = nrows; return 0; } int pg_dav_propfind_do( WebdavPropfindRequest *rq, WebdavResponse *response, VFS_DIR parent, WebdavResource *resource, struct stat *s) { PgPropfind *pg = rq->userdata; pool_handle_t *pool = rq->sn->pool; PGresult *result = pg->result; WebdavVFSProperties vfsprops = pg->vfsproperties; WSBool vfsprops_set = 0; // are live properties added to the response? int64_t current_resource_id = pg->resource_id; for(int r=0;r<pg->nrows;r++) { // columns: // 0: path // 1: resource_id // 2: parent_id // 3: nodename // 4: iscollection // 5: lastmodified // 6: creationdate // 7: contentlength // 8: etag // 9: property prefix // 10: property xmlns // 11: property name // 12: property lang // 13: property nsdeflist // 14: property value char *path = PQgetvalue(result, r, 0); char *res_id = PQgetvalue(result, r, 1); char *iscollection_str = PQgetvalue(result, r, 4); WSBool iscollection = iscollection_str && iscollection_str[0] == 't'; int64_t resource_id; if(!util_strtoint(res_id, &resource_id)) { log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id); return 1; } if(resource_id != current_resource_id) { // create a href string for the new resource // if the resource is a collection, it should have a trailing '/' size_t pathlen = strlen(path); if(pathlen == 0) { log_ereport(LOG_FAILURE, "pg_dav_propfind_do: query returned invalid path"); return 1; } if(pathlen > PG_MAX_PATH_LEN) { log_ereport(LOG_FAILURE, "pg_dav_propfind_do: path too long: resource_id: %s", res_id); return 1; } char *newres_href = pool_malloc(pool, (pathlen*3)+2); util_uri_escape(newres_href, path); if(iscollection && path[pathlen-1] != '/') { size_t newres_href_len = strlen(newres_href); newres_href[newres_href_len] = '/'; newres_href[newres_href_len+1] = '\0'; } // new resource resource = response->addresource(response, newres_href); vfsprops_set = FALSE; current_resource_id = resource_id; } // standard webdav live properties if(!vfsprops_set) { if(vfsprops.getresourcetype) { if(iscollection) { resource->addproperty(resource, webdav_resourcetype_collection(), 200); } else { resource->addproperty(resource, webdav_resourcetype_empty(), 200); } } char *lastmodified = PQgetvalue(result, r, 5); char *contentlength = PQgetvalue(result, r, 7); time_t t = pg_convert_timestamp(lastmodified); if(vfsprops.getlastmodified) { struct tm tm; gmtime_r(&t, &tm); char buf[HTTP_DATE_LEN+1]; strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, &tm); webdav_resource_add_dav_stringproperty(resource, pool, "getlastmodified", buf, strlen(buf)); } if(vfsprops.creationdate) { char *creationdate = PQgetvalue(result, r, 6); webdav_resource_add_dav_stringproperty(resource, pool, "creationdate", creationdate, strlen(creationdate)); } if(vfsprops.getcontentlength && !iscollection) { webdav_resource_add_dav_stringproperty(resource, pool, "getcontentlength", contentlength, strlen(contentlength)); } if(vfsprops.getetag) { char *etag = PQgetvalue(result, r, 8); if(!PQgetisnull(result, r, 8)) { webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag)); } else { int64_t ctlen; if(util_strtoint(contentlength, &ctlen)) { char etag[MAX_ETAG]; http_format_etag(rq->sn, rq->rq, etag, MAX_ETAG, ctlen, t); webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag)); } } } vfsprops_set = TRUE; } // dead properties if(!PQgetisnull(result, r, 9)) { char *prefix = PQgetvalue(result, r, 9); char *xmlns = PQgetvalue(result, r, 10); char *pname = PQgetvalue(result, r, 11); char *lang = PQgetvalue(result, r, 12); char *nsdef = PQgetvalue(result, r, 13); char *pvalue = PQgetvalue(result, r, 14); int pvalue_len = PQgetlength(result, r, 14); WSBool lang_isnull = PQgetisnull(result, r, 12); WSBool nsdef_isnull = PQgetisnull(result, r, 13); WSBool pvalue_isnull = PQgetisnull(result, r, 14); WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); property->lang = NULL; property->name = pool_strdup(pool, pname); xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs)); memset(namespace, 0, sizeof(struct _xmlNs)); namespace->href = (xmlChar*)pool_strdup(pool, xmlns); namespace->prefix = (xmlChar*)pool_strdup(pool, prefix); property->namespace = namespace; if(!lang_isnull) { property->lang = pool_strdup(pool, lang); } if(!pvalue_isnull) { char *content = pool_malloc(pool, pvalue_len+1); memcpy(content, pvalue, pvalue_len); content[pvalue_len] = '\0'; if(nsdef_isnull) { property->vtype = WS_VALUE_TEXT; property->value.text.str = content; property->value.text.length = pvalue_len; } else { WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef); property->vtype = WS_VALUE_XML_DATA; property->value.data->data = content; property->value.data->length = pvalue_len; property->value.data->namespaces = nslist; } } resource->addproperty(resource, property, 200); } } return 0; } int pg_dav_propfind_finish(WebdavPropfindRequest *rq) { PgPropfind *pg = rq->userdata; pool_handle_t *pool = rq->sn->pool; PGresult *result = pg->result; PQclear(result); return 0; } enum PgDavProp { PG_DAV_PROPPATCH_NOT_ALLOWED = 0, PG_DAV_CREATIONDATE, PG_DAV_DISPLAYNAME, PG_DAV_DEADPROP }; /* * checks if the property can be manipulated */ static enum PgDavProp proppatch_check_dav_prop(const char *name) { if(!strcmp(name, "getlastmodified")) { return PG_DAV_PROPPATCH_NOT_ALLOWED; } else if(!strcmp(name, "getcontentlength")) { return PG_DAV_PROPPATCH_NOT_ALLOWED; } else if(!strcmp(name, "resourcetype")) { return PG_DAV_PROPPATCH_NOT_ALLOWED; } else if(!strcmp(name, "getetag")) { return PG_DAV_PROPPATCH_NOT_ALLOWED; } else if(!strcmp(name, "creationdate")) { return PG_DAV_CREATIONDATE; } else if(!strcmp(name, "displayname")) { return PG_DAV_DISPLAYNAME; } return PG_DAV_DEADPROP; } typedef struct { WebdavProperty *creationdate; WebdavProperty *displayname; int error; } PgProppatchOpResult; typedef int(*pg_proppatch_func)(PgWebdavBackend*, WebdavProppatchRequest*, WebdavResource*, WebdavProperty*, void*); /* * This function iterates the property list 'plist', * analyses if any DAV: property is in the list * and calls opfunc for the each property * * If the property list contains the properties creationdate or displayname, * the pointers to these properties will be stored in the result structure */ static PgProppatchOpResult pg_proppatch_op( PgWebdavBackend *pgdav, WebdavProppatchRequest *request, WebdavResource *response, WebdavPList **plist, enum PgDavProp forbidden_extra, pg_proppatch_func opfunc, void *op_userdata) { PgProppatchOpResult result; result.creationdate = NULL; result.displayname = NULL; result.error = 0; WebdavPListIterator i = webdav_plist_iterator(plist); WebdavPList *cur; while(webdav_plist_iterator_next(&i, &cur)) { WebdavProperty *property = cur->property; WSNamespace *ns = property->namespace; if(!ns) { continue; // maybe we should abort } // check if the property is a DAV: property that requires special // handling // get* properties can't be manipulated // some properties can't be removed if(!strcmp((const char*)ns->href, "DAV:")) { const char *name = property->name; enum PgDavProp davprop = proppatch_check_dav_prop(name); if(davprop != PG_DAV_DEADPROP) { if(davprop == PG_DAV_PROPPATCH_NOT_ALLOWED || davprop == forbidden_extra) { response->addproperty(response, property, 409); } else if(davprop == PG_DAV_CREATIONDATE) { result.creationdate = property; } else if(davprop == PG_DAV_DISPLAYNAME) { result.displayname = property; } webdav_plist_iterator_remove_current(&i); continue; } } // call op func (set, remove specific code) if(opfunc(pgdav, request, response, property, op_userdata)) { result.error = 1; break; } webdav_plist_iterator_remove_current(&i); } return result; } static int pg_dav_set_property( PgWebdavBackend *pgdav, WebdavProppatchRequest *request, WebdavResource *response, WebdavProperty *property, void *userdata) { pool_handle_t *pool = request->sn->pool; WSNamespace *ns = property->namespace; char *resource_id_str = userdata; int ret = 0; // convert the property value to WSXmlData // property->vtype == WS_VALUE_XML_NODE should always be true WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; char *value_str = NULL; char *nsdef_str = NULL; if(property_value) { value_str = property_value->data; if(property_value->namespaces) { nsdef_str = wsxml_nslist2string(pool, property_value->namespaces); if(!nsdef_str) { return 1; // OOM } } } // exec sql const char* params[7] = { resource_id_str, (const char*)ns->prefix, (const char*)ns->href, property->name, NULL, nsdef_str, value_str}; PGresult *result = PQexecParams( pgdav->connection, sql_proppatch_set, 7, // number of parameters NULL, params, // parameter value NULL, NULL, 0); // 0: result in text format if(PQresultStatus(result) != PGRES_COMMAND_OK) { response->addproperty(response, property, 500); //printf(PQerrorMessage(pgdav->connection)); //fflush(stdout); ret = 1; } else { response->addproperty(response, property, 200); } PQclear(result); if(value_str) pool_free(pool, value_str); return ret; } static int pg_dav_remove_property( PgWebdavBackend *pgdav, WebdavProppatchRequest *request, WebdavResource *response, WebdavProperty *property, void *userdata) { pool_handle_t *pool = request->sn->pool; WSNamespace *ns = property->namespace; char *resource_id_str = userdata; int ret = 0; // exec sql const char* params[3] = { resource_id_str, (const char*)ns->href, property->name }; PGresult *result = PQexecParams( pgdav->connection, sql_proppatch_remove, 3, // number of parameters NULL, params, // parameter value NULL, NULL, 0); // 0: result in text format if(PQresultStatus(result) != PGRES_COMMAND_OK) { response->addproperty(response, property, 500); //printf(PQerrorMessage(pgdav->connection)); //fflush(stdout); ret = 1; } PQclear(result); return ret; } int pg_dav_proppatch_do( WebdavProppatchRequest *request, WebdavResource *response, VFSFile *file, WebdavPList **out_set, WebdavPList **out_remove) { PgWebdavBackend *pgdav = request->dav->instance; pool_handle_t *pool = request->sn->pool; char *path = pblock_findkeyval(pb_key_path, request->rq->vars); // check if the resource exists, we also need the resource_id int64_t parent_id; int64_t resource_id; const char *resourcename; WSBool iscollection; int res_errno = 0; int err = pg_resolve_path( pgdav->connection, path, &parent_id, &resource_id, NULL, // OID &resourcename, &iscollection, NULL, // stat NULL, // etag &res_errno); if(err) { return 1; } // because proppatch must be atomic and we have multiple sql // queries and other backends that do stuff that could fail // we need the possibility to reverse all changes // we use a transaction savepoint for this PGresult *result = PQexec(pgdav->connection, "savepoint proppatch;"); ExecStatusType execStatus = PQresultStatus(result); PQclear(result); if(execStatus != PGRES_COMMAND_OK) { return 1; } char resource_id_str[32]; snprintf(resource_id_str, 32, "%" PRId64, resource_id); int ret = 0; PgProppatchOpResult set_res = pg_proppatch_op( pgdav, request, response, out_set, PG_DAV_PROPPATCH_NOT_ALLOWED, pg_dav_set_property, resource_id_str); if(set_res.error) { return 1; } PgProppatchOpResult rm_res = pg_proppatch_op( pgdav, request, response, out_remove, PG_DAV_CREATIONDATE, // creationdate can't be removed pg_dav_remove_property, resource_id_str); if(rm_res.error) { return 1; } return ret; } int pg_dav_proppatch_finish( WebdavProppatchRequest *request, WebdavResource *response, VFSFile *file, WSBool commit) { PgWebdavBackend *pgdav = request->dav->instance; int ret = 0; if(!commit) { PGresult *result = PQexec(pgdav->connection, "rollback to savepoint proppatch;"); if(PQresultStatus(result) != PGRES_COMMAND_OK) { log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: rollback failed: %s", PQerrorMessage(pgdav->connection)); ret = 1; } PQclear(result); } return ret; }