diff -r 09676b559091 -r 60870dbac94f src/server/plugins/postgresql/webdav.c --- a/src/server/plugins/postgresql/webdav.c Tue Apr 26 18:21:25 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.c Sat Apr 30 20:44:38 2022 +0200 @@ -30,10 +30,12 @@ #include "vfs.h" #include "../../util/util.h" +#include "../../util/pblock.h" #include #include + static WebdavBackend pg_webdav_backend = { pg_dav_propfind_init, pg_dav_propfind_do, @@ -237,6 +239,28 @@ ) 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"; + + WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb) { // resourcepool is required char *resource_pool = pblock_findval("resourcepool", pb); @@ -561,7 +585,7 @@ property->namespace = namespace; if(!lang_isnull) { - property->lang = (xmlChar*)pool_strdup(pool, lang); + property->lang = pool_strdup(pool, lang); } if(!pvalue_isnull) { @@ -595,6 +619,184 @@ 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 = property_value ? property_value->data : NULL; + + // TODO: convert WebdavNSList to a string + + // exec sql + const char* params[7] = { resource_id_str, (const char*)ns->prefix, (const char*)ns->href, property->name, NULL, NULL, 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, @@ -602,7 +804,70 @@ WebdavPList **out_set, WebdavPList **out_remove) { - return 1; + 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 + &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( @@ -611,5 +876,15 @@ VFSFile *file, WSBool commit) { - return 1; + 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; }