#include "xattrbackend.h"
#include "webdav.h"
#include "../util/util.h"
#include "../util/libxattr.h"
#include "../util/pblock.h"
#include <inttypes.h>
#include <cx/hash_map.h>
#include <cx/buffer.h>
#include <cx/utils.h>
#include <cx/printf.h>
#include <libxml/tree.h>
static WebdavBackend webdav_xattr_backend = {
webdav_xattr_propfind_init,
webdav_xattr_propfind_do,
webdav_xattr_propfind_finish,
webdav_xattr_proppatch_do,
webdav_xattr_proppatch_finish,
NULL,
NULL,
NULL,
NULL,
0,
NULL,
NULL
};
int webdav_init_xattr_backend(
void) {
if(webdav_register_backend(
"xattr", webdav_xattr_init, webdav_xattr_create)) {
return 1;
}
return 0;
}
void* webdav_xattr_init(ServerConfiguration *cfg,
pool_handle_t *pool, WSConfigNode *config) {
WebdavXAttrRepository *repo = pool_malloc(pool,
sizeof(WebdavXAttrRepository));
if(!repo) {
return NULL;
}
repo->xattr_name =
"webdav_properties";
return repo;
}
WebdavBackend* webdav_xattr_create(Session *sn, Request *rq, pblock *pb,
void *initData) {
WebdavBackend *dav = pool_malloc(sn->pool,
sizeof(WebdavBackend));
if(!dav) {
return NULL;
}
WebdavXAttrBackend *instance = pool_malloc(sn->pool,
sizeof(WebdavXAttrBackend));
if(!instance) {
return NULL;
}
instance->repo = initData;
*dav = webdav_xattr_backend;
dav->instance = instance;
return dav;
}
int webdav_xattr_propfind_init(
WebdavPropfindRequest *rq,
const char *path,
const char *href,
WebdavPList **outplist)
{
if(rq->rq->vfs) {
log_ereport(
LOG_FAILURE,
"webdav-propfind: xattr backend unsupported with non-native VFS");
return 1;
}
XAttrPropfind *xprop = pool_malloc(rq->sn->pool,
sizeof(XAttrPropfind));
if(!xprop) {
return 1;
}
rq->userdata = xprop;
xprop->base_href = href;
xprop->base_path = path;
return 0;
}
int webdav_xattr_propfind_do(
WebdavPropfindRequest *request,
WebdavResponse *response,
VFS_DIR parent,
WebdavResource *resource,
struct stat *s)
{
Session *sn = request->sn;
Request *rq = request->rq;
CxAllocator *a = pool_allocator(sn->pool);
WebdavXAttrBackend *xdav = request->dav->instance;
WebdavXAttrRepository *repo = xdav->repo;
XAttrPropfind *xprop = request->userdata;
const char *path;
char *path_dp =
NULL;
if(!parent) {
path = xprop->base_path;
}
else {
size_t base_href_len = strlen(xprop->base_href);
size_t base_path_len = strlen(xprop->base_path);
char *res_path = resource->href + base_href_len;
size_t res_path_len = strlen(res_path);
path_dp = pool_malloc(sn->pool, base_path_len + res_path_len +
2);
memcpy(path_dp, xprop->base_path, base_path_len);
int s =
0;
if(path_dp[base_path_len-
1] !=
'/' && res_path[
0] !=
'/') {
path_dp[base_path_len] =
'/';
s =
1;
}
memcpy(path_dp + base_path_len + s, res_path, res_path_len);
path_dp[base_path_len + s + res_path_len] =
0;
path = path_dp;
}
ssize_t xattr_data_len =
0;
char *xattr_data = xattr_get_alloc(
sn->pool,
(libxattr_malloc_func)pool_malloc,
(libxattr_free_func)pool_free,
path,
repo->xattr_name,
&xattr_data_len);
if(!xattr_data) {
return 0;
}
CxMap *pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path);
pool_free(sn->pool, xattr_data);
if(!pmap) {
return 1;
}
int err;
if(request->allprop || request->propname) {
err = webdav_xattr_propfind_allprop(request, resource, a, pmap);
}
else {
err = webdav_xattr_propfind_get_requested_properties(request, resource, a, pmap);
}
return err;
}
int webdav_xattr_propfind_finish(WebdavPropfindRequest *rq) {
return 0;
}
int webdav_xattr_propfind_get_requested_properties(
WebdavPropfindRequest *request,
WebdavResource *resource,
CxAllocator *a,
CxMap *pmap)
{
WebdavPListIterator i = webdav_plist_iterator(&request->properties);
WebdavPList *cur;
while(webdav_plist_iterator_next(&i, &cur)) {
WebdavProperty *reqprop = cur->property;
CxHashKey key = webdav_property_key_a(
a,
(
const char*)reqprop->namespace->href,
(
const char*)reqprop->name);
if(!key.data) {
return 1;
}
WebdavProperty *prop = cxMapGet(pmap, key);
if(prop) {
if(resource->addproperty(resource, prop,
200)) {
return 1;
}
}
}
return 0;
}
int webdav_xattr_propfind_allprop(
WebdavPropfindRequest *request,
WebdavResource *resource,
CxAllocator *a,
CxMap *pmap)
{
CxIterator i = cxMapIteratorValues(pmap);
cx_foreach(WebdavProperty*, prop, i) {
if(request->propname) {
prop->vtype =
WS_VALUE_NO_TYPE;
}
if(resource->addproperty(resource, prop,
200)) {
return 1;
}
}
return 0;
}
int webdav_xattr_proppatch_do(
WebdavProppatchRequest *request,
WebdavResource *response,
VFSFile *file,
WebdavPList **setInOut,
WebdavPList **removeInOut)
{
Session *sn = request->sn;
Request *rq = request->rq;
CxAllocator *a = pool_allocator(sn->pool);
WebdavXAttrBackend *xdav = request->dav->instance;
WebdavXAttrRepository *repo = xdav->repo;
char *path = pblock_findkeyval(pb_key_path, rq->vars);
XAttrProppatch *xprop = pool_malloc(sn->pool,
sizeof(XAttrProppatch));
if(!xprop) {
return 1;
}
request->userdata = xprop;
ssize_t xattr_data_len =
0;
char *xattr_data = xattr_get_alloc(
sn->pool,
(libxattr_malloc_func)pool_malloc,
(libxattr_free_func)pool_free,
path,
repo->xattr_name,
&xattr_data_len);
CxMap *pmap;
if(xattr_data) {
pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path);
pool_free(sn->pool, xattr_data);
}
else {
pmap = cxHashMapCreate(a,
CX_STORE_POINTERS, request->setcount +
8);
}
if(!pmap) {
return 1;
}
xprop->properties = pmap;
WebdavPListIterator i = webdav_plist_iterator(removeInOut);
WebdavPList *cur;
while(webdav_plist_iterator_next(&i, &cur)) {
WebdavProperty *prop = cur->property;
if(!prop->namespace || !prop->namespace->prefix) {
log_ereport(
LOG_WARN,
"webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name);
continue;
}
CxHashKey key = webdav_property_key_a(
a,
(
const char*)prop->namespace->href,
(
const char*)prop->name);
if(!key.data) {
cxMapDestroy(pmap);
return 1;
}
void *rmprop = cxMapRemoveAndGet(pmap, key);
cxFree(a, (
void*)key.data);
if(rmprop) {
webdav_plist_iterator_remove_current(&i);
}
}
i = webdav_plist_iterator(setInOut);
while(webdav_plist_iterator_next(&i, &cur)) {
WebdavProperty *prop = cur->property;
if(!prop->namespace || !prop->namespace->prefix) {
log_ereport(
LOG_WARN,
"webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name);
continue;
}
if(webdav_xattr_put_prop(pmap, prop)) {
cxMapDestroy(pmap);
return 1;
}
webdav_plist_iterator_remove_current(&i);
}
return 0;
}
int webdav_xattr_proppatch_finish(
WebdavProppatchRequest *request,
WebdavResource *response,
VFSFile *file,
WSBool commit)
{
Session *sn = request->sn;
Request *rq = request->rq;
CxAllocator *a = pool_allocator(sn->pool);
WebdavXAttrBackend *xdav = request->dav->instance;
WebdavXAttrRepository *repo = xdav->repo;
XAttrProppatch *xprop = request->userdata;
char *path = pblock_findkeyval(pb_key_path, rq->vars);
int ret =
0;
if(commit) {
cxmutstr new_data = webdav_xattr_serialze_map(a, xprop->properties);
if(new_data.ptr) {
if(xattr_set(path, repo->xattr_name, new_data.ptr, new_data.length)) {
ret =
1;
}
}
else {
ret =
1;
}
}
return 0;
}
static int get_next_line(cxstring data,
size_t *pos, cxstring *line) {
size_t p = *pos;
cxstring str = cx_strsubs(data, p);
size_t i;
int skip =
0;
for(i=
0;i<str.length;i++) {
if(str.ptr[i] ==
'\n') {
skip =
1;
break;
}
}
p += i;
*pos = p + skip;
*line = cx_strsubsl(str,
0, i);
return i >
0 ?
TRUE :
FALSE;
}
static int webdav_xattr_parse_elm(cxstring line, cxstring *name, cxstring *prefix, cxstring *xmlns, cxstring *lang) {
cxstring s_xmlns =
CX_STR(
"xmlns:");
if(!cx_strprefix(line, s_xmlns)) {
return 1;
}
line.ptr +=
6;
line.length -=
6;
size_t i;
size_t token_end =
0;
for(i=
0;i<line.length;i++) {
if(line.ptr[i] ==
'=') {
token_end = i;
break;
}
}
if(token_end ==
0) {
return 1;
}
*prefix = cx_strn(line.ptr, token_end);
if(token_end +
4 > line.length) {
return 1;
}
if(line.ptr[token_end +
1] !=
'\"') {
return 1;
}
line.ptr += token_end +
2;
line.length -= token_end +
2;
int escape =
0;
token_end =
0;
for(i=
0;i<line.length;i++) {
if(line.ptr[i] ==
'\\') {
escape =
1;
continue;
}
else if(!escape && line.ptr[i] ==
'\"') {
token_end = i;
break;
}
escape =
0;
}
if(token_end ==
0) {
return 1;
}
*xmlns = cx_strn(line.ptr, token_end);
if(token_end +
2 > line.length) {
return 1;
}
line.ptr += token_end +
2;
line.length -= token_end +
2;
*name = cx_strtrim(line);
if(name->length ==
0) {
return 1;
}
if(prefix->length ==
0) {
return 1;
}
if(xmlns->length ==
0) {
return 1;
}
return 0;
}
static int webdav_xattr_parse_ns(cxstring line, cxstring *prefix, cxstring *href) {
size_t i =
0;
for(i=
1;i<line.length;i++) {
if(line.ptr[i] ==
':') {
break;
}
}
if(i ==
0 || i+
1 >= line.length) {
return 1;
}
cxstring pre;
pre.ptr = line.ptr;
pre.length = i;
*prefix = pre;
*href = cx_strsubs(line, i+
1);
return 0;
}
int webdav_xattr_put_prop(CxMap *pmap, WebdavProperty *prop) {
CxHashKey key = webdav_property_key_a(
pmap->allocator,
(
const char*)prop->namespace->href,
(
const char*)prop->name);
if(!key.data) {
return 1;
}
int ret = cxMapPut(pmap, key, prop);
cxFree(pmap->allocator, (
void*)key.data);
return ret;
}
CxMap* webdav_xattr_parse_data(CxAllocator *a,
const char *data,
size_t len,
const char *path) {
CxMap *pmap = cxHashMapCreate(a,
CX_STORE_POINTERS,
32);
if(!pmap) {
return NULL;
}
cxstring dat = cx_strn(data, len);
cxstring s_elm =
CX_STR(
"prop ");
cxstring s_ns =
CX_STR(
"ns ");
cxstring s_data =
CX_STR(
"data ");
WebdavProperty *prop =
NULL;
WebdavNSList *ns_begin =
NULL;
WebdavNSList *ns_end =
NULL;
int error =
0;
size_t elmno =
0;
cxstring line;
size_t pos =
0;
while(get_next_line(dat, &pos, &line)) {
if(cx_strprefix(line, s_elm)) {
if(prop) {
if(webdav_xattr_put_prop(pmap, prop)) {
error =
1;
break;
}
}
line = cx_strsubs(line,
5);
cxstring name;
cxstring prefix;
cxstring xmlns;
cxstring lang;
if(webdav_xattr_parse_elm(line, &name, &prefix, &xmlns, &lang)) {
log_ereport(
LOG_FAILURE,
"webdav xattr backend: file %s: invalid xattr format[%d]: cannot parse elm line",
path,
elmno);
error =
1;
break;
}
prop = cxMalloc(a,
sizeof(WebdavProperty));
if(!prop) {
error =
1;
break;
}
ZERO(prop,
sizeof(WebdavProperty));
ns_begin =
NULL;
ns_end =
NULL;
WSNamespace *ns = cxMalloc(a,
sizeof(WSNamespace));
if(!ns) {
error =
1;
break;
}
ZERO(ns,
sizeof(WSNamespace));
char *name_str = cx_strdup_a(a, name).ptr;
char *prefix_str = cx_strdup_a(a, prefix).ptr;
char *xmlns_str = cx_strdup_a(a, xmlns).ptr;
if(!(name_str && prefix_str && xmlns_str)) {
error =
1;
break;
}
ns->prefix = (
const xmlChar*)prefix_str;
ns->href = (
const xmlChar*)xmlns_str;
prop->name = name_str;
prop->namespace = ns;
elmno++;
}
else if(prop) {
if(cx_strprefix(line, s_ns)) {
line = cx_strsubs(line,
3);
cxstring prefix;
cxstring href;
if(webdav_xattr_parse_ns(line, &prefix, &href)) {
log_ereport(
LOG_FAILURE,
"webdav xattr backend: file %s: invalid xattr format[%d]: cannot parse ns",
path,
elmno);
error =
1;
break;
}
WSNamespace *ns_decl = cxMalloc(a,
sizeof(WSNamespace));
if(!ns_decl) {
error =
1;
break;
}
ZERO(ns_decl,
sizeof(WSNamespace));
ns_decl->prefix = (
const xmlChar*)cx_strdup_a(a, prefix).ptr;
ns_decl->href = (
const xmlChar*)cx_strdup_a(a, href).ptr;
if(!(ns_decl->prefix && ns_decl->href)) {
error =
1;
break;
}
if(webdav_nslist_add(a->data, &ns_begin, &ns_end, ns_decl)) {
error =
1;
break;
}
}
else if(cx_strprefix(line, s_data)) {
line = cx_strsubs(line,
5);
int64_t data_len =
0;
if(!util_strtoint(line.ptr, &data_len)) {
log_ereport(
LOG_FAILURE,
"webdav xattr backend: file %s: invalid xattr format[%d]: number expected after data",
path,
elmno);
error =
1;
break;
}
if(data_len >
0) {
if(data_len > dat.length - pos) {
log_ereport(
LOG_FAILURE,
"webdav xattr backend: file %s: invalid data length %" PRId64
" in prop %d",
path,
data_len,
elmno);
error =
1;
break;
}
cxstring propdata;
propdata.ptr = dat.ptr + pos;
propdata.length = data_len;
pos += data_len;
cxmutstr propdata_cp = cx_strdup_a(a, propdata);
if(!propdata_cp.ptr) {
error =
1;
break;
}
prop->vtype =
WS_VALUE_XML_DATA;
prop->value.data.namespaces = ns_begin;
prop->value.data.data = propdata_cp.ptr;
prop->value.data.length = propdata_cp.length;
if(pos < dat.length && dat.ptr[pos] ==
'\n') {
pos++;
}
}
}
else {
log_ereport(
LOG_FAILURE,
"webdav xattr backend: file %s: invalid xattr format[%d]: unknown element",
path,
elmno);
error =
1;
break;
}
}
}
if(prop) {
if(webdav_xattr_put_prop(pmap, prop)) {
error =
1;
}
}
if(error) {
cxMapDestroy(pmap);
pmap =
NULL;
}
return pmap;
}
cxmutstr webdav_xattr_serialze_map(CxAllocator *a, CxMap *pmap) {
pool_handle_t *pool = a->data;
CxBuffer buf;
if(cxBufferInit(&buf,
NULL,
8192, a,
CX_BUFFER_AUTO_EXTEND)) {
return (cxmutstr){
NULL,
0};
}
CxIterator i = cxMapIteratorValues(pmap);
cx_foreach(WebdavProperty*, prop, i) {
WSXmlData *property_value =
NULL;
if(prop->vtype ==
WS_VALUE_XML_NODE) {
property_value = wsxml_node2data(pool, prop->value.node);
}
else if(prop->vtype ==
WS_VALUE_XML_DATA) {
property_value = &prop->value.data;
}
int ret = cx_bprintf(&buf,
"prop xmlns:%s=\"%s\" %s\n", prop->namespace->prefix, prop->namespace->href, prop->name);
if(ret <=
0) {
pool_free(pool, buf.space);
return (cxmutstr){
NULL,
0};
}
if(property_value) {
WebdavNSList *ns = property_value->namespaces;
while(ns) {
ret = cx_bprintf(&buf,
"ns %s:%s\n", ns->namespace->prefix, ns->namespace->href);
if(ret <=
0) {
pool_free(pool, buf.space);
return (cxmutstr){
NULL,
0};
}
ns = ns->next;
}
ret = cx_bprintf(&buf,
"data %zu\n", property_value->length);
if(ret <=
0) {
pool_free(pool, buf.space);
return (cxmutstr){
NULL,
0};
}
cxBufferWrite(property_value->data,
1, property_value->length, &buf);
if(cxBufferPut(&buf,
'\n') <
0) {
pool_free(pool, buf.space);
return (cxmutstr){
NULL,
0};
}
}
}
return (cxmutstr){buf.space, buf.size};
}