#include "webdav.h"
#include "vfs.h"
#include "config.h"
#include "../../util/util.h"
#include "../../util/pblock.h"
#include "../../daemon/http.h"
#include <cx/buffer.h>
#include <cx/utils.h>
#include <cx/printf.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,
NULL,
NULL,
NULL,
0,
NULL,
NULL
};
static const char *sql_propfind_cte_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";
static const char *sql_propfind_select =
"\
select\n";
static const char *sql_propfind_ppath_depth0 =
"\
$2::text as ppath,\n";
static const char *sql_propfind_ppath_depth1 =
"\
case when r.resource_id = $1 then $2\n\
else $2 || ''/'' || r.nodename\n\
end as ppath,\n";
static const char *sql_propfind_ppath_depth_infinity =
"\
case when r.resource_id = $1 then $2\n\
else $2 || r.ppath\n\
end as ppath,\n";
static const char *sql_propfind_cols =
"\
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";
static const char *sql_propfind_from_table =
"\
from Resource r\n";
static const char *sql_propfind_from_cte =
"\
from resolvepath r\n";
static const char *sql_propfind_propjoin_allprop =
"\
left join Property p on r.resource_id = p.resource_id\n";
static const char *sql_propfind_propjoin_plist =
"\
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";
static const char *sql_propfind_where_depth0 =
"\
where r.resource_id = $1\n";
static const char *sql_propfind_where_depth1 =
"\
where r.resource_id = $1 or r.parent_id = $1\n";
static const char *sql_propfind_order_depth1 =
"\
order by case when r.resource_id = $1 then 0 else 1 end, r.nodename, r.resource_id";
static const char *sql_propfind_order_depth_infinity =
"\
order by replace(ppath, ''/'', chr(1)), r.resource_id";
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;";
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(cfg, pool, config);
}
WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb,
void *initData) {
PgRepository *repo = initData;
char *resource_pool;
if(repo) {
resource_pool = repo->resourcepool.ptr;
}
else {
resource_pool = pblock_findval(
"resourcepool", pb);
if(!resource_pool) {
log_ereport(
LOG_MISCONFIG,
"pg_webdav_create: missing resourcepool parameter");
return NULL;
}
}
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, repo, resdata);
}
WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, 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;
instance->repository = repo;
snprintf(instance->root_resource_id_str,
32,
"%" PRId64, repo->root_resource_id);
return webdav;
}
WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) {
return NULL;
}
static void buf_addstr_escaped(CxBuffer *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 ==
'\\') {
cxBufferPut(buf,
'\\');
}
cxBufferPut(buf, c);
}
}
int pg_create_property_param_arrays(WebdavPList *plist, CxBuffer *xmlns, CxBuffer *pname) {
cxBufferPut(xmlns,
'{');
cxBufferPut(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) {
cxBufferPut(xmlns,
',');
cxBufferPut(pname,
',');
}
}
plist = plist->next;
}
int r1 = cxBufferWrite(
"}\0",
2,
1, xmlns) ==
0;
int r2 = cxBufferWrite(
"}\0",
2,
1, pname) ==
0;
return r1+r2 !=
0;
}
static int propfind_ext_cmp(
const void *f1,
const void *f2) {
const PgPropfindExtCol *e1 = f1;
const PgPropfindExtCol *e2 = f2;
if(e1->ext->tableindex != e2->ext->tableindex) {
return e1->ext->tableindex < e2->ext->tableindex ? -
1 :
1;
}
return 0;
}
static int pg_create_propfind_query(
WebdavPropfindRequest *rq,
WSBool iscollection,
PgPropfindExtCol *ext,
size_t numext,
CxBuffer *sql)
{
PgWebdavBackend *pgdav = rq->dav->instance;
PgRepository *repo = pgdav->repository;
int depth = !iscollection ?
0 : rq->depth;
if(depth == -
1) {
cxBufferPutString(sql, sql_propfind_cte_recursive);
}
cxBufferPutString(sql, sql_propfind_select);
switch(depth) {
case 0: cxBufferPutString(sql, sql_propfind_ppath_depth0);
break;
case 1: cxBufferPutString(sql, sql_propfind_ppath_depth1);
break;
case -
1: cxBufferPutString(sql, sql_propfind_ppath_depth_infinity);
break;
}
cxBufferPutString(sql, sql_propfind_cols);
if(ext) {
if(rq->allprop) {
for(
int i=
0;i<repo->ntables;i++) {
cx_bprintf(sql,
",x%d.*\n", i);
}
}
else {
for(
int i=
0;i<numext;i++) {
PgPropfindExtCol e = ext[i];
cx_bprintf(sql,
",x%d.%s\n", e.ext->tableindex, e.ext->column);
}
}
}
cxBufferPutString(sql, depth == -
1 ? sql_propfind_from_cte : sql_propfind_from_table);
cxBufferPutString(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist);
if(ext) {
if(rq->allprop) {
for(
int i=
0;i<repo->ntables;i++) {
cx_bprintf(sql,
"left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[i].table, i, i);
}
}
else {
int tab = -
1;
for(
int i=
0;i<numext;i++) {
PgPropfindExtCol e = ext[i];
if(e.ext->tableindex != tab) {
tab = e.ext->tableindex;
cx_bprintf(sql,
"left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[tab].table, tab, tab);
}
}
}
}
if(depth ==
0) {
cxBufferPutString(sql, sql_propfind_where_depth0);
}
else if(depth ==
1) {
cxBufferPutString(sql, sql_propfind_where_depth1);
}
if(depth ==
1) {
cxBufferPutString(sql, sql_propfind_order_depth1);
}
else if(depth == -
1) {
cxBufferPutString(sql, sql_propfind_order_depth_infinity);
}
cxBufferWrite(
";\0",
1,
2, sql);
return 0;
}
int pg_dav_propfind_init(
WebdavPropfindRequest *rq,
const char *path,
const char *href,
WebdavPList **outplist)
{
PgWebdavBackend *pgdav = rq->dav->instance;
CxAllocator *a = pool_allocator(rq->sn->pool);
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,
pgdav->root_resource_id_str,
&parent_id,
&resource_id,
NULL,
&resourcename,
&iscollection,
NULL,
NULL,
&res_errno);
if(err) {
if(res_errno ==
ENOENT) {
protocol_status(rq->sn, rq->rq,
PROTOCOL_NOT_FOUND,
NULL);
}
return 1;
}
char resource_id_str[
32];
snprintf(resource_id_str,
32,
"%" PRId64, resource_id);
pblock_nvinsert(
"resource_id", resource_id_str, rq->rq->vars);
PgPropfindExtCol *ext;
size_t numext;
if(pgdav->repository->ntables ==
0) {
ext =
NULL;
numext =
0;
}
else {
numext = pgdav->repository->prop_ext->size;
ext = pool_calloc(rq->sn->pool, numext,
sizeof(PgPropfindExtCol));
if(rq->allprop) {
CxIterator i = cxMapIteratorValues(pgdav->repository->prop_ext);
int j =
0;
cx_foreach(PgPropertyStoreExt *, cfg_ext, i) {
PgPropfindExtCol extcol;
extcol.ext = cfg_ext;
extcol.field_num = -
1;
ext[j++] = extcol;
}
}
else {
WebdavPListIterator i = webdav_plist_iterator(outplist);
WebdavPList *cur;
int j =
0;
while(webdav_plist_iterator_next(&i, &cur)) {
WSNamespace *ns = cur->property->namespace;
if(ns) {
CxHashKey pkey = webdav_property_key((
const char*)ns->href, cur->property->name);
if(!pkey.data) {
return 1;
}
PgPropertyStoreExt *cfg_ext = cxMapGet(pgdav->repository->prop_ext, pkey);
free((
void*)pkey.data);
if(cfg_ext) {
PgPropfindExtCol extcol;
extcol.ext = cfg_ext;
extcol.field_num = -
1;
ext[j++] = extcol;
webdav_plist_iterator_remove_current(&i);
}
}
}
numext = j;
}
qsort(ext, numext,
sizeof(PgPropfindExtCol), propfind_ext_cmp);
}
const char *query =
NULL;
CxBuffer sql;
if(cxBufferInit(&sql,
NULL,
2048, a,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
return 1;
}
if(pg_create_propfind_query(rq, iscollection, ext, numext, &sql)) {
cxBufferDestroy(&sql);
return 1;
}
query = sql.space;
log_ereport(
LOG_DEBUG,
"pg_dav_propfind_init query: %.*s\n", (
int)sql.size, sql.space);
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';
CxBuffer xmlns_buf;
CxBuffer pname_buf;
WSBool buf_initialized =
FALSE;
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;
if(cxBufferInit(&xmlns_buf,
NULL, bufsize, a,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
return 1;
}
if(cxBufferInit(&pname_buf,
NULL, bufsize, a,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
cxBufferDestroy(&sql);
cxBufferDestroy(&xmlns_buf);
return 1;
}
if(pg_create_property_param_arrays(*outplist, &xmlns_buf, &pname_buf)) {
cxBufferDestroy(&sql);
cxBufferDestroy(&xmlns_buf);
cxBufferDestroy(&pname_buf);
return 1;
}
buf_initialized =
TRUE;
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,
NULL,
params,
NULL,
NULL,
0);
int nrows = PQntuples(result);
pool_free(rq->sn->pool, href_param);
cxBufferDestroy(&sql);
if(buf_initialized) {
cxBufferDestroy(&xmlns_buf);
cxBufferDestroy(&pname_buf);
}
if(nrows <
1) {
if(PQresultStatus(result) !=
PGRES_TUPLES_OK) {
log_ereport(
LOG_FAILURE,
"pg_dav_propfind_init: %s", PQerrorMessage(pgdav->connection));
}
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;
pg->ext = ext;
pg->numext = numext;
if(ext) {
int nfields = PQnfields(result);
CxMap *fieldmap = cxHashMapCreate(pool_allocator(rq->sn->pool),
sizeof(
int), nfields);
if(!fieldmap) {
PQclear(result);
return 1;
}
for(
int i=
15;i<nfields;i++) {
char *name = PQfname(result, i);
if(name) {
if(cxMapPut(fieldmap, name, &i)) {
PQclear(result);
return 1;
}
}
}
for(
int i=
0;i<numext;i++) {
PgPropfindExtCol *c = &ext[i];
int *fieldnum = cxMapGet(fieldmap, c->ext->column);
c->field_num = *fieldnum;
}
cxMapDestroy(fieldmap);
}
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;
WSBool extprops_set =
0;
int64_t current_resource_id = pg->resource_id;
for(
int r=
0;r<pg->nrows;r++) {
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) {
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';
}
resource = response->addresource(response, newres_href);
vfsprops_set =
FALSE;
extprops_set =
FALSE;
current_resource_id = resource_id;
}
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;
}
if(!extprops_set) {
if(pg->ext) {
for(
int extc=
0;extc<pg->numext;extc++) {
PgPropfindExtCol ext = pg->ext[extc];
int fieldnum = ext.field_num;
if(!PQgetisnull(result, r, fieldnum)) {
char *ext_value = PQgetvalue(result, r, fieldnum);
int ext_value_len = PQgetlength(result, r, fieldnum);
char ext_xmlns_prefix[
32];
snprintf(ext_xmlns_prefix,
32,
"x%d", ext.ext->tableindex);
WebdavProperty *property = pool_malloc(pool,
sizeof(WebdavProperty));
property->lang =
NULL;
property->name = pool_strdup(pool, ext.ext->name);
xmlNs *namespace = pool_malloc(pool,
sizeof(xmlNs));
memset(namespace,
0,
sizeof(
struct _xmlNs));
namespace->href = (xmlChar*)pool_strdup(pool, ext.ext->ns);
namespace->prefix = (xmlChar*)pool_strdup(pool, ext_xmlns_prefix);
property->namespace = namespace;
char *content = pool_malloc(pool, ext_value_len+
1);
memcpy(content, ext_value, ext_value_len);
content[ext_value_len] =
'\0';
WebdavNSList *nslist = pool_malloc(pool,
sizeof(WebdavNSList));
nslist->namespace = namespace;
nslist->prev =
NULL;
nslist->next =
NULL;
property->vtype =
WS_VALUE_XML_DATA;
property->value.data.data = content;
property->value.data.length = ext_value_len;
property->value.data.namespaces = nslist;
resource->addproperty(resource, property,
200);
}
}
}
extprops_set =
TRUE;
}
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
};
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*);
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;
}
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;
}
}
if(opfunc(pgdav, request, response, property, op_userdata)) {
result.error =
1;
break;
}
webdav_plist_iterator_remove_current(&i);
}
return result;
}
static PgPropertyStoreExt* pg_proppatch_prop_get_ext(PgWebdavBackend *pgdav, WebdavProperty *property) {
CxHashKey pkey = webdav_property_key((
const char*)property->namespace->href, property->name);
if(!pkey.data) {
return NULL;
}
PgPropertyStoreExt *ext = cxMapGet(pgdav->repository->prop_ext, pkey);
free((
void*)pkey.data);
return ext;
}
#define PG_PROPPATCH_EXT_SET 0
#define PG_PROPPATCH_EXT_REMOVE 1
static int pg_proppatch_add_ext_prop(
pool_handle_t *pool,
PgWebdavBackend *pgdav,
PgProppatch *proppatch,
WebdavProperty *property,
PgPropertyStoreExt *ext,
int proppatch_op)
{
PgProppatchExtProp *ext_prop = pool_malloc(pool,
sizeof(PgProppatchExtProp));
if(!ext_prop) {
return 1;
}
ext_prop->column = ext;
ext_prop->property = property;
ext_prop->next =
NULL;
CxAllocator *a = pool_allocator(pool);
proppatch->ext[ext->tableindex].isused =
TRUE;
PgProppatchExtProp **list_begin;
PgProppatchExtProp **list_end;
if(proppatch_op ==
PG_PROPPATCH_EXT_SET) {
list_begin = &proppatch->ext[ext->tableindex].set_begin;
list_end = &proppatch->ext[ext->tableindex].set_end;
}
else {
list_begin = &proppatch->ext[ext->tableindex].remove_begin;
list_end = &proppatch->ext[ext->tableindex].remove_end;
}
cx_linked_list_add((
void**)list_begin, (
void**)list_end, -
1, offsetof(PgProppatchExtProp, next), ext_prop);
proppatch->extensions_used =
TRUE;
return 0;
}
static int pg_dav_set_property(
PgWebdavBackend *pgdav,
WebdavProppatchRequest *request,
WebdavResource *response,
WebdavProperty *property,
void *userdata)
{
pool_handle_t *pool = request->sn->pool;
PgProppatch *proppatch = request->userdata;
WSNamespace *ns = property->namespace;
if(proppatch->ext && ns) {
PgPropertyStoreExt *ext = pg_proppatch_prop_get_ext(pgdav, property);
if(ext) {
return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, ext,
PG_PROPPATCH_EXT_SET);
}
}
char *resource_id_str = userdata;
int ret =
0;
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;
}
}
}
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,
NULL,
params,
NULL,
NULL,
0);
if(PQresultStatus(result) !=
PGRES_COMMAND_OK) {
response->addproperty(response, property,
500);
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;
PgProppatch *proppatch = request->userdata;
WSNamespace *ns = property->namespace;
if(proppatch->ext && ns) {
PgPropertyStoreExt *ext = pg_proppatch_prop_get_ext(pgdav, property);
if(ext) {
return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, ext,
PG_PROPPATCH_EXT_REMOVE);
}
}
char *resource_id_str = userdata;
int ret =
0;
const char* params[
3] = { resource_id_str, (
const char*)ns->href, property->name };
PGresult *result = PQexecParams(
pgdav->connection,
sql_proppatch_remove,
3,
NULL,
params,
NULL,
NULL,
0);
if(PQresultStatus(result) !=
PGRES_COMMAND_OK) {
response->addproperty(response, property,
500);
ret =
1;
}
PQclear(result);
return ret;
}
static CxBuffer* ext_row_create_insert_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table,
char *** params,
size_t *nparams) {
pool_handle_t *pool = request->sn->pool;
CxBuffer *sql = pool_malloc(pool,
sizeof(CxBuffer));
if(!sql) {
return NULL;
}
if(cxBufferInit(sql,
NULL,
1024, pool_allocator(pool),
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
pool_free(pool, sql);
return NULL;
}
size_t pg_nparams = cx_linked_list_size(ext->set_begin, offsetof(PgProppatchExtProp, next)) +
1;
char** pg_params = pool_calloc(pool, pg_nparams,
sizeof(
char*));
if(!pg_params) {
cxBufferDestroy(sql);
pool_free(pool, sql);
return NULL;
}
cxBufferPutString(sql,
"insert into ");
cxBufferPutString(sql, table->table);
cxBufferPutString(sql,
"(resource_id");
for(PgProppatchExtProp *prop=ext->set_begin;prop;prop=prop->next) {
cx_bprintf(sql,
",%s", prop->column->name);
}
cxBufferPutString(sql,
") values ($1\n");
int i =
1;
for(PgProppatchExtProp *prop=ext->set_begin;prop;prop=prop->next) {
WebdavProperty *property = prop->property;
WSXmlData *property_value = property->vtype ==
WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) :
NULL;
char *value_str =
NULL;
if(property_value) {
value_str = property_value->data;
if(property_value->namespaces) {
pool_free(pool, params);
cxBufferDestroy(sql);
return NULL;
}
}
pg_params[i] = value_str;
cx_bprintf(sql,
",$%d", ++i);
}
cxBufferPutString(sql,
");");
*params = pg_params;
*nparams = pg_nparams;
return sql;
}
static CxBuffer* ext_row_create_update_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table,
char *** params,
size_t *nparams) {
pool_handle_t *pool = request->sn->pool;
CxBuffer *sql = pool_malloc(pool,
sizeof(CxBuffer));
if(!sql) {
return NULL;
}
if(cxBufferInit(sql,
NULL,
1024, pool_allocator(pool),
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS)) {
pool_free(pool, sql);
return NULL;
}
cxBufferPutString(sql,
"update ");
cxBufferPutString(sql, table->table);
cxBufferPutString(sql,
" set\n");
size_t pg_nparams = cx_linked_list_size(ext->set_begin, offsetof(PgProppatchExtProp, next)) +
1;
char** pg_params = pool_calloc(pool, pg_nparams,
sizeof(
char*));
if(!pg_params) {
cxBufferDestroy(sql);
pool_free(pool, sql);
return NULL;
}
int i =
1;
for(PgProppatchExtProp *prop=ext->set_begin;prop;prop=prop->next) {
WebdavProperty *property = prop->property;
WSXmlData *property_value = property->vtype ==
WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) :
NULL;
char *value_str =
NULL;
if(property_value) {
value_str = property_value->data;
if(property_value->namespaces) {
pool_free(pool, params);
cxBufferDestroy(sql);
return NULL;
}
}
pg_params[i] = value_str;
cx_bprintf(sql,
" %s = $%d,\n", prop->column->name, ++i);
}
for(PgProppatchExtProp *prop=ext->remove_begin;prop;prop=prop->next) {
cx_bprintf(sql,
" %s = NULL,\n", prop->column->name);
}
if(sql->pos ==
0) {
cxBufferDestroy(sql);
pool_free(pool, pg_params);
return NULL;
}
if(sql->space[sql->pos-
2] ==
',') {
sql->space[sql->pos-
2] =
' ';
}
cxBufferPutString(sql,
"where resource_id = $1 ;");
cxBufferPut(sql,
'\0');
*params = pg_params;
*nparams = pg_nparams;
return sql;
}
int ext_row_insert(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
PgWebdavBackend *pgdav = request->dav->instance;
PgProppatch *proppatch = request->userdata;
pool_handle_t *pool = request->sn->pool;
char **params;
size_t nparam;
CxBuffer *sql = ext_row_create_insert_query(request, ext, table, ¶ms, &nparam);
if(!sql) {
return 1;
}
char resource_id_str[
32];
snprintf(resource_id_str,
32,
"%" PRId64, proppatch->resource_id);
params[
0] = resource_id_str;
PGresult *result = PQexecParams(
pgdav->connection,
sql->space,
nparam,
NULL,
(
const char *
const *)params,
NULL,
NULL,
0);
cxBufferDestroy(sql);
int ret =
1;
if(PQresultStatus(result) ==
PGRES_COMMAND_OK) {
char *nrows_affected = PQcmdTuples(result);
if(nrows_affected[
0] ==
'1') {
ret =
0;
}
else {
log_ereport(
LOG_FAILURE,
"pg: extension row insert failed");
}
}
else {
log_ereport(
LOG_FAILURE,
"pg: extension row insert failed: %s", PQresultErrorMessage(result));
}
PQclear(result);
return ret;
}
int ext_row_update(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) {
PgWebdavBackend *pgdav = request->dav->instance;
PgProppatch *proppatch = request->userdata;
pool_handle_t *pool = request->sn->pool;
char **params;
size_t nparam;
CxBuffer *sql = ext_row_create_update_query(request, ext, table, ¶ms, &nparam);
if(!sql) {
return 1;
}
char resource_id_str[
32];
snprintf(resource_id_str,
32,
"%" PRId64, proppatch->resource_id);
params[
0] = resource_id_str;
PGresult *result = PQexecParams(
pgdav->connection,
sql->space,
nparam,
NULL,
(
const char *
const *)params,
NULL,
NULL,
0);
cxBufferDestroy(sql);
int ret =
1;
if(PQresultStatus(result) ==
PGRES_COMMAND_OK) {
char *nrows_affected = PQcmdTuples(result);
if(nrows_affected[
0] ==
'1') {
ret =
0;
}
else if(nrows_affected[
0] ==
'0') {
ret = ext_row_insert(request, ext, table);
}
}
else {
log_ereport(
LOG_FAILURE,
"pg: extension row update failed: %s", PQresultErrorMessage(result));
}
PQclear(result);
return ret;
}
static int pg_dav_update_extension_tables(WebdavProppatchRequest *request) {
PgWebdavBackend *pgdav = request->dav->instance;
PgProppatch *proppatch = request->userdata;
for(
int i=
0;i<proppatch->numext;i++) {
if(proppatch->ext[i].isused) {
if(ext_row_update(request, &proppatch->ext[i], &pgdav->repository->tables[i])) {
return 1;
}
}
}
return 0;
}
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);
PgProppatch proppatch;
proppatch.extensions_used =
FALSE;
if(pgdav->repository->ntables ==
0) {
proppatch.ext =
NULL;
proppatch.numext =
0;
}
else {
proppatch.numext = pgdav->repository->ntables;
proppatch.ext = pool_calloc(request->sn->pool, proppatch.numext,
sizeof(PgProppatchExt));
if(!proppatch.ext) {
return 1;
}
}
request->userdata = &proppatch;
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,
pgdav->root_resource_id_str,
&parent_id,
&resource_id,
NULL,
&resourcename,
&iscollection,
NULL,
NULL,
&res_errno);
if(err) {
return 1;
}
proppatch.resource_id = resource_id;
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);
pblock_nvinsert(
"resource_id", resource_id_str, request->rq->vars);
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,
pg_dav_remove_property,
resource_id_str);
if(rm_res.error) {
return 1;
}
if(proppatch.extensions_used) {
ret = pg_dav_update_extension_tables(request);
}
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) {
log_ereport(
LOG_VERBOSE,
"proppatch: rollback");
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;
}