#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <libxml/tree.h>
#include "utils.h"
#include "session.h"
#include "methods.h"
#include "crypto.h"
#include <cx/buffer.h>
#include <cx/utils.h>
#include <cx/hash_map.h>
#include <cx/printf.h>
#include <cx/mempool.h>
#include <cx/array_list.h>
#include "resource.h"
#include "xml.h"
#include "davqlexec.h"
#define xstreq(a,b) xmlStrEqual(
BAD_CAST a,
BAD_CAST b)
DavResource* dav_resource_new(DavSession *sn,
const char *path) {
char *parent = util_parent_path(path);
const char *name = util_resource_name(path);
char *href = dav_session_create_plain_href(sn, path);
DavResource *res = dav_resource_new_full(sn, parent, name, href);
free(parent);
return res;
}
DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent,
const char *name) {
char *path = util_concat_path(parent->path, name);
char *href = dav_session_create_plain_href(sn, path);
DavResource *res = dav_resource_new_full(sn, parent->path, name, href);
free(path);
return res;
}
DavResource* dav_resource_new_href(DavSession *sn,
const char *href) {
DavResource *res = cxCalloc(sn->mp->allocator,
1,
sizeof(DavResource));
res->session = sn;
resource_set_info(res, href);
res->data = resource_data_new(sn);
return res;
}
DavResource* dav_resource_new_full(DavSession *sn,
const char *parent_path,
const char *name,
char *href) {
cxstring n = cx_str(name);
if(n.length >
0 && href) {
for(
int i=
0;i<n.length-
1;i++) {
char c = n.ptr[i];
if(c ==
'/' || c ==
'\\') {
n = cx_str(util_resource_name(href));
break;
}
}
}
if(n.length >
0 && n.ptr[n.length-
1] ==
'/') {
n.length--;
}
DavResource *res = cxCalloc(sn->mp->allocator,
1,
sizeof(DavResource));
res->session = sn;
res->name = cx_strdup_a(sn->mp->allocator, n).ptr;
char *path = util_concat_path(parent_path, name);
res->path = dav_session_strdup(sn, path);
res->href = href;
res->data = resource_data_new(sn);
if(href) {
dav_session_cache_path(sn, cx_str(path), cx_str(href));
}
free(path);
return res;
}
void resource_free_properties(DavSession *sn, CxMap *properties) {
if(!properties)
return;
CxIterator i = cxMapIteratorValues(properties);
cx_foreach(DavProperty*, property, i) {
dav_session_free(sn, property);
}
cxMapDestroy(properties);
}
void dav_resource_free(DavResource *res) {
DavSession *sn = res->session;
dav_session_free(sn, res->name);
dav_session_free(sn, res->path);
if(res->href) {
dav_session_free(sn, res->href);
}
DavResourceData *data = res->data;
resource_free_properties(sn, data->properties);
resource_free_properties(sn, data->crypto_properties);
if(data->set) {
CxIterator i = cxListIterator(data->set);
cx_foreach(DavProperty *, p, i) {
dav_session_free(sn, p->ns->name);
if(p->ns->prefix) {
dav_session_free(sn, p->ns->prefix);
}
dav_session_free(sn, p->ns);
dav_session_free(sn, p->name);
dav_free_xml_node_sn(sn, p->value);
dav_session_free(sn, p);
}
}
if(data->remove) {
CxIterator i = cxListIterator(data->remove);
cx_foreach(DavProperty *, p, i) {
dav_session_free(sn, p->ns->name);
if(p->ns->prefix) {
dav_session_free(sn, p->ns->prefix);
}
dav_session_free(sn, p->ns);
dav_session_free(sn, p->name);
dav_session_free(sn, p);
}
}
if(data->crypto_set) {
CxIterator i = cxListIterator(data->crypto_set);
cx_foreach(DavProperty *, p, i) {
dav_session_free(sn, p->ns->name);
if(p->ns->prefix) {
dav_session_free(sn, p->ns->prefix);
}
dav_session_free(sn, p->ns);
dav_session_free(sn, p->name);
dav_free_xml_node_sn(sn, p->value);
dav_session_free(sn, p);
}
}
if(data->crypto_remove) {
CxIterator i = cxListIterator(data->crypto_remove);
cx_foreach(DavProperty *, p, i) {
dav_session_free(sn, p->ns->name);
if(p->ns->prefix) {
dav_session_free(sn, p->ns->prefix);
}
dav_session_free(sn, p->ns);
dav_session_free(sn, p->name);
dav_session_free(sn, p);
}
}
if(!data->read && data->content) {
dav_session_free(sn, data->content);
}
dav_session_free(sn, data);
dav_session_free(sn, res);
}
void dav_resource_free_all(DavResource *res) {
DavResource *child = res->children;
dav_resource_free(res);
while(child) {
DavResource *next = child->next;
dav_resource_free_all(child);
child = next;
}
}
void resource_set_href(DavResource *res, cxstring href) {
res->href = cx_strdup_a(res->session->mp->allocator, href).ptr;
}
void resource_set_info(DavResource *res,
const char *href_str) {
char *url_str =
NULL;
curl_easy_getinfo(res->session->handle,
CURLINFO_EFFECTIVE_URL, &url_str);
cxstring name = cx_str(util_resource_name(href_str));
cxstring href = cx_str(href_str);
cxstring base_href = cx_str(util_url_path(res->session->base_url));
cxstring path = cx_strsubs(href, base_href.length -
1);
const CxAllocator *a = res->session->mp->allocator;
CURL *handle = res->session->handle;
int nlen =
0;
char *uname = curl_easy_unescape(handle, name.ptr, name.length , &nlen);
int plen =
0;
char *upath = curl_easy_unescape(handle, path.ptr, path.length, &plen);
res->name = cx_strdup_a(a, cx_strn(uname, nlen)).ptr;
res->href = cx_strdup_a(a, href).ptr;
res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr;
curl_free(uname);
curl_free(upath);
}
DavResourceData* resource_data_new(DavSession *sn) {
DavResourceData *data = cxMalloc(
sn->mp->allocator,
sizeof(DavResourceData));
if(!data) {
return NULL;
}
data->properties = cxHashMapCreate(sn->mp->allocator,
CX_STORE_POINTERS,
32);
data->crypto_properties =
NULL;
data->set =
NULL;
data->remove =
NULL;
data->crypto_set =
NULL;
data->crypto_remove =
NULL;
data->read =
NULL;
data->content =
NULL;
data->seek =
NULL;
data->length =
0;
return data;
}
char* dav_resource_get_href(DavResource *resource) {
if(!resource->href) {
resource->href = dav_session_get_href(
resource->session,
resource->path);
}
return resource->href;
}
void resource_add_prop(DavResource *res,
const char *ns,
const char *name, DavXmlNode *val) {
DavSession *sn = res->session;
DavNamespace *namespace = dav_session_malloc(sn,
sizeof(DavNamespace));
namespace->prefix =
NULL;
namespace->name = dav_session_strdup(sn, ns);
DavProperty *prop = dav_session_malloc(sn,
sizeof(DavProperty));
prop->name = dav_session_strdup(sn, name);
prop->ns = namespace;
prop->value = val;
cxmutstr keystr = dav_property_key(ns, name);
CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
cxMapPut(((DavResourceData*)res->data)->properties, key, prop);
free(keystr.ptr);
}
void resource_add_property(DavResource *res,
const char *ns,
const char *name, xmlNode *val) {
if(!val) {
return;
}
resource_add_prop(res, ns, name, dav_convert_xml(res->session, val));
}
void resource_add_string_property(DavResource *res,
char *ns,
char *name,
char *val) {
if(!val) {
return;
}
resource_add_prop(res, ns, name, dav_text_node(res->session, val));
}
void resource_set_crypto_properties(DavResource *res, CxMap *cprops) {
DavResourceData *data = res->data;
resource_free_properties(res->session, data->crypto_properties);
data->crypto_properties = cprops;
}
DavXmlNode* resource_get_property(DavResource *res,
const char *ns,
const char *name) {
cxmutstr keystr = dav_property_key(ns, name);
CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
DavXmlNode *ret = resource_get_property_k(res, key);
free(keystr.ptr);
return ret;
}
DavXmlNode* resource_get_encrypted_property(DavResource *res,
const char *ns,
const char *name) {
cxmutstr keystr = dav_property_key(ns, name);
CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
DavXmlNode *ret = resource_get_encrypted_property_k(res, key);
free(keystr.ptr);
return ret;
}
DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key) {
DavResourceData *data = (DavResourceData*)res->data;
DavProperty *property = cxMapGet(data->properties, key);
return property ? property->value :
NULL;
}
DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) {
DavResourceData *data = (DavResourceData*)res->data;
DavProperty *property = cxMapGet(data->crypto_properties, key);
return property ? property->value :
NULL;
}
cxmutstr dav_property_key(
const char *ns,
const char *name) {
return dav_property_key_a(cxDefaultAllocator, ns, name);
}
cxmutstr dav_property_key_a(
const CxAllocator *a,
const char *ns,
const char *name) {
cxstring ns_str = cx_str(ns);
cxstring name_str = cx_str(name);
return cx_strcat_a(a,
4, ns_str,
CX_STR(
"\0"), name_str,
CX_STR(
"\0"));
}
void resource_add_child(DavResource *parent, DavResource *child) {
child->next =
NULL;
if(parent->children) {
DavResource *last = parent->children;
while(last->next) {
last = last->next;
}
last->next = child;
child->prev = last;
}
else {
child->prev =
NULL;
parent->children = child;
}
child->parent = parent;
}
static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion *cr) {
if(!(res1 && res2)) {
return 0;
}
int ret;
if(cr->type ==
0) {
switch(cr->column.resprop) {
case DAVQL_RES_NAME: {
ret = strcmp(res1->name, res2->name);
break;
}
case DAVQL_RES_PATH: {
ret = strcmp(res1->path, res2->path);
break;
}
case DAVQL_RES_HREF: {
ret = strcmp(res1->href, res2->href);
break;
}
case DAVQL_RES_CONTENTLENGTH: {
int c = res1->contentlength == res2->contentlength;
ret = c ?
0 : (res1->contentlength < res2->contentlength?-
1:
1);
break;
}
case DAVQL_RES_CONTENTTYPE: {
ret = strcmp(res1->contenttype, res2->contenttype);
break;
}
case DAVQL_RES_CREATIONDATE: {
int c = res1->creationdate == res2->creationdate;
ret = c ?
0 : (res1->creationdate < res2->creationdate?-
1:
1);
break;
}
case DAVQL_RES_LASTMODIFIED: {
int c = res1->lastmodified == res2->lastmodified;
ret = c ?
0 : (res1->lastmodified < res2->lastmodified?-
1:
1);
break;
}
case DAVQL_RES_ISCOLLECTION: {
int c = res1->iscollection == res2->iscollection;
ret = c ?
0 : (res1->iscollection < res2->iscollection?-
1:
1);
break;
}
default: ret =
0;
}
}
else if(cr->type ==
1) {
DavXmlNode *xvalue1 = resource_get_property_k(res1, cr->column.property);
DavXmlNode *xvalue2 = resource_get_property_k(res2, cr->column.property);
char *value1 = dav_xml_getstring(xvalue1);
char *value2 = dav_xml_getstring(xvalue2);
if(!value1) {
ret = value2 ? -
1 :
0;
}
else if(!value2) {
ret = value1 ?
1 :
0;
}
else {
ret = strcmp(value1, value2);
}
}
else {
return 0;
}
return cr->descending ? -ret : ret;
}
void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr) {
if(!ordercr) {
resource_add_child(parent, child);
return;
}
child->parent = parent;
if(!parent->children) {
child->next =
NULL;
child->prev =
NULL;
parent->children = child;
}
else {
DavResource *resource = parent->children;
while(resource) {
int r =
0;
CxIterator i = cxListIterator(ordercr);
cx_foreach(DavOrderCriterion*, cr, i) {
r = resource_cmp(child, resource, cr);
if(r !=
0) {
break;
}
}
if(r <
0) {
child->prev = resource->prev;
child->next = resource;
if(resource->prev) {
resource->prev->next = child;
}
else {
parent->children = child;
}
resource->prev = child;
break;
}
if(!resource->next) {
child->prev = resource;
child->next =
NULL;
resource->next = child;
break;
}
else {
resource = resource->next;
}
}
}
}
char* dav_get_string_property(DavResource *res,
char *name) {
char *pns;
char *pname;
dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
if(!pns || !pname) {
return NULL;
}
return dav_get_string_property_ns(res, pns, pname);
}
char* dav_get_string_property_ns(DavResource *res,
char *ns,
char *name) {
DavXmlNode *prop = dav_get_property_ns(res, ns, name);
if(!prop) {
return NULL;
}
return dav_xml_getstring(prop);
}
DavXmlNode* dav_get_property(DavResource *res,
char *name) {
char *pns;
char *pname;
dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
if(!pns || !pname) {
return NULL;
}
return dav_get_property_ns(res, pns, pname);
}
static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted,
const char *ns,
const char *name) {
if(!ns || !name) {
return NULL;
}
DavResourceData *data = res->data;
DavXmlNode *property =
NULL;
CxList *remove_list =
NULL;
CxList *set_list =
NULL;
if(encrypted) {
if(!data->crypto_properties) {
return NULL;
}
property = resource_get_encrypted_property(res, ns, name);
remove_list = data->crypto_remove;
set_list = data->crypto_set;
}
else {
property = resource_get_property(res, ns, name);
remove_list = data->remove;
set_list = data->set;
}
if(property && remove_list) {
CxIterator i = cxListIterator(remove_list);
cx_foreach(DavProperty*, p, i) {
if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
return NULL;
}
}
}
if(set_list) {
CxIterator i = cxListIterator(set_list);
cx_foreach(DavProperty*, p, i) {
if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
return p->value;
}
}
}
return property;
}
DavXmlNode* dav_get_property_ns(DavResource *res,
const char *ns,
const char *name) {
DavXmlNode *property_value = get_property_ns(res,
FALSE, ns, name);
if(!property_value &&
DAV_DECRYPT_PROPERTIES(res->session)) {
property_value = get_property_ns(res,
TRUE, ns, name);
}
return property_value;
}
DavXmlNode* dav_get_encrypted_property_ns(DavResource *res,
const char *ns,
const char *name) {
return get_property_ns(res,
TRUE, ns, name);
}
static DavProperty* createprop(DavSession *sn,
const char *ns,
const char *name) {
DavProperty *property = dav_session_malloc(sn,
sizeof(DavProperty));
property->name = dav_session_strdup(sn, name);
property->value =
NULL;
DavNamespace *namespace = dav_session_malloc(sn,
sizeof(DavNamespace));
namespace->prefix =
NULL;
namespace->name = dav_session_strdup(sn, ns);
property->ns = namespace;
return property;
}
int dav_set_string_property(DavResource *res,
char *name,
char *value) {
char *pns;
char *pname;
dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
if(!pns) {
res->session->errorstr =
"Property namespace not found";
return 1;
}
dav_set_string_property_ns(res, pns, pname, value);
return 0;
}
static int add2propertylist(
const CxAllocator *a, CxList **list, DavProperty *property) {
if(!*list) {
CxList *newlist = cxLinkedListCreate(a,
NULL,
CX_STORE_POINTERS);
if(!newlist) {
return 1;
}
*list = newlist;
}
cxListAdd(*list, property);
return 0;
}
void dav_set_string_property_ns(DavResource *res,
char *ns,
char *name,
char *value) {
DavSession *sn = res->session;
const CxAllocator *a = res->session->mp->allocator;
DavResourceData *data = res->data;
DavProperty *property = createprop(res->session, ns, name);
property->value = dav_text_node(res->session, value);
if(
DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
add2propertylist(a, &data->crypto_set, property);
}
else {
add2propertylist(a, &data->set, property);
}
}
void dav_set_property(DavResource *res,
char *name, DavXmlNode *value) {
char *pns;
char *pname;
dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
dav_set_property_ns(res, pns, pname, value);
}
void dav_set_property_ns(DavResource *res,
char *ns,
char *name, DavXmlNode *value) {
DavSession *sn = res->session;
const CxAllocator *a = sn->mp->allocator;
DavResourceData *data = res->data;
DavProperty *property = createprop(sn, ns, name);
property->value = value;
if(
DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
add2propertylist(a, &data->crypto_set, property);
}
else {
add2propertylist(a, &data->set, property);
}
}
void dav_remove_property(DavResource *res,
char *name) {
char *pns;
char *pname;
dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
dav_remove_property_ns(res, pns, pname);
}
void dav_remove_property_ns(DavResource *res,
char *ns,
char *name) {
DavSession *sn = res->session;
DavResourceData *data = res->data;
const CxAllocator *a = res->session->mp->allocator;
DavProperty *property = createprop(res->session, ns, name);
if(
DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
add2propertylist(a, &data->crypto_remove, property);
}
else {
add2propertylist(a, &data->remove, property);
}
}
void dav_set_encrypted_property_ns(DavResource *res,
char *ns,
char *name, DavXmlNode *value) {
const CxAllocator *a = res->session->mp->allocator;
DavResourceData *data = res->data;
DavProperty *property = createprop(res->session, ns, name);
property->value = value;
add2propertylist(a, &data->crypto_set, property);
}
void dav_set_encrypted_string_property_ns(DavResource *res,
char *ns,
char *name,
char *value) {
const CxAllocator *a = res->session->mp->allocator;
DavResourceData *data = res->data;
DavProperty *property = createprop(res->session, ns, name);
property->value = dav_text_node(res->session, value);
add2propertylist(a, &data->crypto_set, property);
}
void dav_remove_encrypted_property_ns(DavResource *res,
char *ns,
char *name) {
DavResourceData *data = res->data;
const CxAllocator *a = res->session->mp->allocator;
DavProperty *property = createprop(res->session, ns, name);
add2propertylist(a, &data->crypto_remove, property);
}
static int compare_propname(
const void *a,
const void *b) {
const DavPropName *p1 = a;
const DavPropName *p2 = b;
int result = strcmp(p1->ns, p2->ns);
if(result) {
return result;
}
else {
return strcmp(p1->name, p2->name);
}
}
DavPropName* dav_get_property_names(DavResource *res,
size_t *count) {
DavResourceData *data = res->data;
*count = cxMapSize(data->properties);
DavPropName *names = dav_session_calloc(
res->session,
*count,
sizeof(DavPropName));
CxIterator i = cxMapIteratorValues(data->properties);
cx_foreach(DavProperty*, value, i) {
DavPropName *name = &names[i.index];
name->ns = value->ns->name;
name->name = value->name;
}
qsort(names, *count,
sizeof(DavPropName), compare_propname);
return names;
}
void dav_set_content(DavResource *res,
void *stream, dav_read_func read_func, dav_seek_func seek_func) {
DavResourceData *data = res->data;
data->content = stream;
data->read = read_func;
data->seek = seek_func;
data->length =
0;
}
void dav_set_content_data(DavResource *res,
char *content,
size_t length) {
DavSession *sn = res->session;
DavResourceData *data = res->data;
data->content = dav_session_malloc(sn, length);
memcpy(data->content, content, length);
data->read =
NULL;
data->seek =
NULL;
data->length = length;
}
void dav_set_content_length(DavResource *res,
size_t length) {
DavResourceData *data = res->data;
data->length = length;
}
int dav_load(DavResource *res) {
CxBuffer *rqbuf = create_allprop_propfind_request();
int ret = dav_propfind(res->session, res, rqbuf);
cxBufferFree(rqbuf);
return ret;
}
int dav_load_prop(DavResource *res, DavPropName *properties,
size_t numprop) {
CxMempool *mp = cxMempoolCreate(
64,
NULL);
const CxAllocator *a = mp->allocator;
CxList *proplist = cxArrayListCreate(a,
NULL,
sizeof(DavProperty), numprop);
for(
size_t i=
0;i<numprop;i++) {
DavProperty p;
p.name = properties[i].name;
p.ns = cxMalloc(a,
sizeof(DavNamespace));
p.ns->name = properties[i].ns;
if(!strcmp(properties[i].ns,
"DAV:")) {
p.ns->prefix =
"D";
}
else {
p.ns->prefix = cx_asprintf_a(a,
"x%d", (
int)i).ptr;
}
p.value =
NULL;
cxListAdd(proplist, &p);
}
CxBuffer *rqbuf = create_propfind_request(res->session, proplist,
"propfind",
0);
int ret = dav_propfind(res->session, res, rqbuf);
cxBufferFree(rqbuf);
cxMempoolDestroy(mp);
return ret;
}
static void init_hash_stream(HashStream *hstr,
void *stream, dav_read_func readfn, dav_seek_func seekfn) {
hstr->sha =
NULL;
hstr->stream = stream;
hstr->read = readfn;
hstr->seek = seekfn;
hstr->error =
0;
}
static size_t dav_read_h(
void *buf,
size_t size,
size_t nelm,
void *stream) {
HashStream *s = stream;
if(!s->sha) {
s->sha = dav_hash_init();
}
size_t r = s->read(buf, size, nelm, s->stream);
dav_hash_update(s->sha, buf, r);
return r;
}
static int dav_seek_h(
void *stream,
long offset,
int whence) {
HashStream *s = stream;
if(offset ==
0 && whence ==
SEEK_SET) {
unsigned char buf[
DAV_SHA256_DIGEST_LENGTH];
dav_hash_final(s->sha, buf);
s->sha =
NULL;
}
else {
s->error =
1;
}
return s->seek(s->stream, offset, whence);
}
int dav_store(DavResource *res) {
DavSession *sn = res->session;
DavResourceData *data = res->data;
util_set_url(sn, dav_resource_get_href(res));
DavLock *lock = dav_get_lock(sn, res->path);
char *locktoken = lock ? lock->token :
NULL;
if(data->content) {
curl_easy_setopt(sn->handle,
CURLOPT_XFERINFOFUNCTION, dav_session_put_progress);
curl_easy_setopt(sn->handle,
CURLOPT_XFERINFODATA, res);
curl_easy_setopt(sn->handle,
CURLOPT_NOPROGRESS,
0L);
int encryption =
DAV_ENCRYPT_CONTENT(sn) && sn->key;
CURLcode ret;
if(encryption) {
AESEncrypter *enc =
NULL;
CxBuffer *buf =
NULL;
if(data->read) {
enc = aes_encrypter_new(
sn->key,
data->content,
data->read,
data->seek);
}
else {
buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator,
0);
buf->size = data->length;
enc = aes_encrypter_new(
sn->key,
buf,
(dav_read_func)cxBufferRead,
(dav_seek_func)cxBufferSeek);
}
ret = do_put_request(
sn,
locktoken,
TRUE,
enc,
(dav_read_func)aes_read,
(dav_seek_func)aes_encrypter_reset,
0);
dav_get_hash(&enc->sha256, (
unsigned char*)data->hash);
char *enc_hash = aes_encrypt(data->hash,
DAV_SHA256_DIGEST_LENGTH, sn->key);
aes_encrypter_close(enc);
if(buf) {
cxBufferFree(buf);
}
if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
free(enc_hash);
curl_easy_setopt(sn->handle,
CURLOPT_XFERINFOFUNCTION,
NULL);
curl_easy_setopt(sn->handle,
CURLOPT_NOPROGRESS,
1L);
return 1;
}
resource_add_string_property(res,
DAV_NS,
"crypto-hash", enc_hash);
free(enc_hash);
}
else if((sn->flags &
DAV_SESSION_STORE_HASH) ==
DAV_SESSION_STORE_HASH) {
HashStream hstr;
CxBuffer *iobuf =
NULL;
if(!data->read) {
iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator,
0);
iobuf->size = data->length;
init_hash_stream(
&hstr,
iobuf,
(dav_read_func)cxBufferRead,
(dav_seek_func)cxBufferSeek);
}
else {
init_hash_stream(
&hstr,
data->content,
data->read,
data->seek);
}
ret = do_put_request(
sn,
locktoken,
TRUE,
&hstr,
dav_read_h,
(dav_seek_func)dav_seek_h,
data->length);
if(hstr.sha) {
dav_hash_final(hstr.sha, (
unsigned char*)data->hash);
char *hash = util_hexstr((
unsigned char*)data->hash,
32);
dav_set_string_property_ns(res,
DAV_NS,
"content-hash", hash);
free(hash);
}
}
else {
ret = do_put_request(
sn,
locktoken,
TRUE,
data->content,
data->read,
data->seek,
data->length);
}
curl_easy_setopt(sn->handle,
CURLOPT_XFERINFOFUNCTION,
NULL);
curl_easy_setopt(sn->handle,
CURLOPT_NOPROGRESS,
1L);
long status =
0;
curl_easy_getinfo(sn->handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
res->session->error =
0;
if(!data->read) {
cxFree(sn->mp->allocator, data->content);
}
data->content =
NULL;
data->read =
NULL;
data->length =
0;
}
else {
dav_session_set_error(sn, ret, status);
return 1;
}
}
if(
DAV_ENCRYPT_PROPERTIES(sn) && sn->key && (data->crypto_set || data->crypto_remove)) {
DavResource *crypto_res = dav_resource_new_href(sn, res->href);
int ret =
1;
if(crypto_res) {
CxBuffer *rqbuf = create_cryptoprop_propfind_request();
ret = dav_propfind(res->session, res, rqbuf);
cxBufferFree(rqbuf);
}
if(!ret) {
DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res,
DAV_NS,
"crypto-prop");
CxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
if(!crypto_props) {
crypto_props = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
32);
}
if(data->crypto_remove) {
CxIterator i = cxListIterator(data->crypto_remove);
cx_foreach(DavProperty *, property, i) {
if(cxMapSize(crypto_props) ==
0) {
break;
}
cxmutstr key = dav_property_key(property->ns->name, property->name);
DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length));
if(existing_prop) {
}
free(key.ptr);
}
}
if(data->crypto_set) {
CxIterator i = cxListIterator(data->crypto_set);
cx_foreach(DavProperty *, property, i) {
cxmutstr keystr = dav_property_key(property->ns->name, property->name);
CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key);
cxMapPut(crypto_props, key, property);
if(existing_prop) {
}
free(keystr.ptr);
}
}
DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props);
if(crypto_prop_value) {
DavProperty *new_crypto_prop = createprop(sn,
DAV_NS,
"crypto-prop");
new_crypto_prop->value = crypto_prop_value;
add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop);
}
dav_resource_free(crypto_res);
}
if(ret) {
return 1;
}
}
int r =
0;
sn->error =
DAV_OK;
if(data->set || data->remove >
0) {
CxBuffer *request = create_proppatch_request(data);
CxBuffer *response = cxBufferCreate(
NULL,
1024, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CURLcode ret = do_proppatch_request(sn, locktoken, request, response);
long status =
0;
curl_easy_getinfo (sn->handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && status ==
207) {
data->set =
NULL;
data->remove =
NULL;
}
else {
dav_session_set_error(sn, ret, status);
r = -
1;
}
cxBufferFree(request);
cxBufferFree(response);
}
return r;
}
#if LIBCURL_VERSION_MAJOR >=
7 &&
LIBCURL_VERSION_MINOR >=
32
static void set_progressfunc(DavResource *res) {
CURL *handle = res->session->handle;
curl_easy_setopt(handle,
CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
curl_easy_setopt(handle,
CURLOPT_XFERINFODATA, res);
curl_easy_setopt(handle,
CURLOPT_NOPROGRESS,
0L);
}
static void unset_progressfunc(DavResource *res) {
CURL *handle = res->session->handle;
curl_easy_setopt(handle,
CURLOPT_XFERINFOFUNCTION,
NULL);
curl_easy_setopt(handle,
CURLOPT_XFERINFODATA,
NULL);
curl_easy_setopt(handle,
CURLOPT_NOPROGRESS,
1L);
}
#else
static void set_progressfunc(DavResource *res) {
}
static void unset_progressfunc(DavResource *res) {
}
#endif
int dav_get_content(DavResource *res,
void *stream, dav_write_func write_fnc) {
DavSession *sn = res->session;
CURL *handle = sn->handle;
util_set_url(res->session, dav_resource_get_href(res));
AESDecrypter *dec =
NULL;
DavKey *key =
NULL;
if(
DAV_DECRYPT_CONTENT(sn)) {
char *keyname = dav_get_string_property_ns(res,
DAV_NS,
"crypto-key");
if(keyname) {
key = dav_context_get_key(sn->context, keyname);
if(key) {
dec = aes_decrypter_new(key, stream, write_fnc);
stream = dec;
write_fnc = (dav_write_func)aes_write;
}
}
}
curl_easy_setopt(handle,
CURLOPT_HTTPHEADER,
NULL);
curl_easy_setopt(handle,
CURLOPT_CUSTOMREQUEST,
NULL);
curl_easy_setopt(handle,
CURLOPT_PUT,
0L);
curl_easy_setopt(handle,
CURLOPT_UPLOAD,
0L);
curl_easy_setopt(handle,
CURLOPT_WRITEFUNCTION, write_fnc);
curl_easy_setopt(handle,
CURLOPT_WRITEDATA, stream);
if(sn->get_progress) {
set_progressfunc(res);
}
long status =
0;
CURLcode ret = dav_session_curl_perform(sn, &status);
if(sn->get_progress) {
unset_progressfunc(res);
}
char *hash =
NULL;
if(dec) {
aes_decrypter_shutdown(dec);
unsigned char sha[
DAV_SHA256_DIGEST_LENGTH];
dav_get_hash(&dec->sha256, sha);
hash = util_hexstr(sha,
DAV_SHA256_DIGEST_LENGTH);
aes_decrypter_close(dec);
}
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
int verify_failed =
0;
if(
DAV_DECRYPT_CONTENT(sn) && key) {
char *res_hash = dav_get_string_property_ns(res,
DAV_NS,
"crypto-hash");
if(res_hash) {
size_t len =
0;
char *dec_hash = aes_decrypt(res_hash, &len, key);
char *hex_hash = util_hexstr((
unsigned char*)dec_hash, len);
if(strcmp(hash, hex_hash)) {
verify_failed =
1;
}
free(dec_hash);
free(hex_hash);
}
}
if(hash) {
free(hash);
}
if(verify_failed) {
res->session->error =
DAV_CONTENT_VERIFICATION_ERROR;
return 1;
}
res->session->error =
DAV_OK;
return 0;
}
else {
if(hash) {
free(hash);
}
dav_session_set_error(res->session, ret, status);
return 1;
}
}
DavResource* dav_create_child(DavResource *parent,
char *name) {
DavResource *res = dav_resource_new_child(parent->session, parent, name);
if(dav_create(res)) {
dav_resource_free(res);
return NULL;
}
else {
return res;
}
}
int dav_delete(DavResource *res) {
CURL *handle = res->session->handle;
util_set_url(res->session, dav_resource_get_href(res));
DavLock *lock = dav_get_lock(res->session, res->path);
char *locktoken = lock ? lock->token :
NULL;
CxBuffer *response = cxBufferCreate(
NULL,
4096, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CURLcode ret = do_delete_request(res->session, locktoken, response);
long status =
0;
curl_easy_getinfo (handle,
CURLINFO_RESPONSE_CODE, &status);
int r =
0;
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
res->session->error =
DAV_OK;
res->exists =
0;
}
else {
dav_session_set_error(res->session, ret, status);
r =
1;
}
cxBufferFree(response);
return r;
}
static int create_ancestors(DavSession *sn,
char *href,
char *path) {
CURL *handle = sn->handle;
CURLcode code;
DavLock *lock = dav_get_lock(sn, path);
char *locktoken = lock ? lock->token :
NULL;
long status =
0;
int ret =
0;
if(strlen(path) <=
1) {
return 0;
}
char *p = util_parent_path(path);
char *h = util_parent_path(href);
for(
int i=
0;i<
2;i++) {
util_set_url(sn, h);
code = do_mkcol_request(sn, locktoken);
curl_easy_getinfo(handle,
CURLINFO_RESPONSE_CODE, &status);
if(status ==
201) {
char *name = (
char*)util_resource_name(p);
int len = strlen(name);
if(name[len -
1] ==
'/') {
name[len -
1] =
'\0';
}
if(resource_add_crypto_info(sn, h, name,
NULL)) {
sn->error =
DAV_ERROR;
dav_session_set_errstr(sn,
"Cannot set crypto properties for ancestor");
}
break;
}
else if(status ==
405) {
break;
}
else if(status ==
409) {
if(create_ancestors(sn, h, p)) {
ret =
1;
break;
}
}
else {
dav_session_set_error(sn, code, status);
ret =
1;
break;
}
}
free(p);
free(h);
return ret;
}
static int create_resource(DavResource *res,
int *status) {
DavSession *sn = res->session;
CURL *handle = sn->handle;
util_set_url(sn, dav_resource_get_href(res));
DavLock *lock = dav_get_lock(res->session, res->path);
char *locktoken = lock ? lock->token :
NULL;
CURLcode code;
if(res->iscollection) {
code = do_mkcol_request(sn, locktoken);
}
else {
code = do_put_request(sn, locktoken,
TRUE,
"",
NULL,
NULL,
0);
}
long s =
0;
curl_easy_getinfo(handle,
CURLINFO_RESPONSE_CODE, &s);
*status = s;
if(code ==
CURLE_OK && (s >=
200 && s <
300)) {
sn->error =
DAV_OK;
if(!resource_add_crypto_info(sn, res->href, res->name,
NULL)) {
CxBuffer *rqbuf = create_propfind_request(sn,
NULL,
"propfind",
0);
int ret = dav_propfind(sn, res, rqbuf);
cxBufferFree(rqbuf);
return ret;
}
else {
return 1;
}
}
else {
dav_session_set_error(sn, code, s);
return 1;
}
}
int dav_create(DavResource *res) {
int status;
if(!create_resource(res, &status)) {
res->exists =
1;
return 0;
}
if(status ==
403 || status ==
409 || status ==
404) {
if(create_ancestors(res->session, res->href, res->path)) {
return 1;
}
}
return create_resource(res, &status);
}
int dav_exists(DavResource *res) {
if(!dav_load_prop(res,
NULL,
0)) {
res->exists =
1;
return 1;
}
else {
if(res->session->error ==
DAV_NOT_FOUND) {
res->exists =
0;
}
return 0;
}
}
static int dav_cp_mv_url(DavResource *res,
char *desturl, _Bool copy, _Bool override) {
DavSession *sn = res->session;
CURL *handle = sn->handle;
util_set_url(sn, dav_resource_get_href(res));
DavLock *lock = dav_get_lock(sn, res->path);
char *locktoken = lock ? lock->token :
NULL;
CURLcode ret = do_copy_move_request(sn, desturl, locktoken, copy, override);
long status =
0;
curl_easy_getinfo (handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
return 0;
}
else {
dav_session_set_error(sn, ret, status);
return 1;
}
}
static int dav_cp_mv(DavResource *res,
char *newpath, _Bool copy, _Bool override) {
char *dest = dav_session_get_href(res->session, newpath);
char *desturl = util_get_url(res->session, dest);
dav_session_free(res->session, dest);
int ret = dav_cp_mv_url(res, desturl, copy, override);
free(desturl);
return ret;
}
int dav_copy(DavResource *res,
char *newpath) {
return dav_cp_mv(res, newpath, true, false);
}
int dav_move(DavResource *res,
char *newpath) {
return dav_cp_mv(res, newpath, false, false);
}
int dav_copy_o(DavResource *res,
char *newpath, DavBool override) {
return dav_cp_mv(res, newpath, true, override);
}
int dav_move_o(DavResource *res,
char *newpath, DavBool override) {
return dav_cp_mv(res, newpath, false, override);
}
int dav_copyto(DavResource *res,
char *url, DavBool override) {
return dav_cp_mv_url(res, url, true, override);
}
int dav_moveto(DavResource *res,
char *url, DavBool override) {
return dav_cp_mv_url(res, url, false, override);
}
int dav_lock(DavResource *res) {
return dav_lock_t(res,
0);
}
int dav_lock_t(DavResource *res,
time_t timeout) {
DavSession *sn = res->session;
CURL *handle = sn->handle;
util_set_url(sn, dav_resource_get_href(res));
CxBuffer *request = create_lock_request();
CxBuffer *response = cxBufferCreate(
NULL,
512, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CURLcode ret = do_lock_request(sn, request, response, timeout);
cxBufferFree(request);
long status =
0;
curl_easy_getinfo (handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
LockDiscovery lock;
int parse_error = parse_lock_response(sn, response, &lock);
cxBufferFree(response);
if(parse_error) {
sn->error =
DAV_ERROR;
return -
1;
}
DavLock *l = dav_create_lock(sn, lock.locktoken, lock.timeout);
free(lock.locktoken);
free(lock.timeout);
int r =
0;
if(res->iscollection) {
r = dav_add_collection_lock(sn, res->path, l);
}
else {
r = dav_add_resource_lock(sn, res->path, l);
}
if(r ==
0) {
return 0;
}
else {
(
void)dav_unlock(res);
sn->error =
DAV_ERROR;
dav_destroy_lock(sn, l);
return -
1;
}
}
else {
dav_session_set_error(sn, ret, status);
cxBufferFree(response);
return -
1;
}
}
int dav_unlock(DavResource *res) {
DavSession *sn = res->session;
CURL *handle = sn->handle;
util_set_url(sn, dav_resource_get_href(res));
DavLock *lock = dav_get_lock(res->session, res->path);
if(!lock) {
sn->error =
DAV_ERROR;
return -
1;
}
CURLcode ret = do_unlock_request(sn, lock->token);
long status =
0;
curl_easy_getinfo (handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && (status >=
200 && status <
300)) {
dav_remove_lock(sn, res->path, lock);
}
else {
dav_session_set_error(sn, ret, status);
return 1;
}
return 0;
}
int resource_add_crypto_info(DavSession *sn,
const char *href,
const char *name,
const char *hash) {
if(!
DAV_IS_ENCRYPTED(sn)) {
return 0;
}
CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
CxBuffer *response = cxBufferCreate(
NULL,
1024, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
util_set_url(sn, href);
CURLcode ret = do_proppatch_request(sn,
NULL, request, response);
cxBufferFree(request);
long status =
0;
curl_easy_getinfo (sn->handle,
CURLINFO_RESPONSE_CODE, &status);
if(ret ==
CURLE_OK && status ==
207) {
sn->error =
DAV_OK;
cxBufferFree(response);
return 0;
}
else {
dav_session_set_error(sn, ret, status);
cxBufferFree(response);
return 1;
}
}
DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) {
if(!sn->key) {
return NULL;
}
CxBuffer *content = cxBufferCreate(
NULL,
2048, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
8);
cxDefineDestructor(nsmap, free);
cxMapPut(nsmap, cx_hash_key_str(
"DAV:"), strdup(
"D"));
cxBufferPutString(content,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
cxBufferPutString(content,
"<D:prop xmlns:D=\"DAV:\">\n");
CxIterator i = cxMapIteratorValues(properties);
cx_foreach(DavProperty*, prop, i) {
DavXmlNode pnode;
pnode.type =
DAV_XML_ELEMENT;
pnode.namespace = prop->ns->name;
pnode.name = prop->name;
pnode.prev =
NULL;
pnode.next =
NULL;
pnode.children = prop->value;
pnode.parent =
NULL;
pnode.attributes =
NULL;
pnode.content =
NULL;
pnode.contentlength =
0;
dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode);
cxBufferPut(content,
'\n');
}
cxBufferPutString(content,
"</D:prop>");
cxMapDestroy(nsmap);
char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
cxBufferDestroy(content);
DavXmlNode *ret =
NULL;
if(crypto_prop_content) {
ret = dav_text_node(sn, crypto_prop_content);
free(crypto_prop_content);
}
return ret;
}
CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) {
if(!node || node->type !=
DAV_XML_TEXT || node->contentlength ==
0) {
return NULL;
}
return parse_crypto_prop_str(sn, key, node->content);
}
CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key,
const char *content) {
size_t len =
0;
char *dec_str = aes_decrypt(content, &len, key);
xmlDoc *doc = xmlReadMemory(dec_str, len,
NULL,
NULL,
0);
free(dec_str);
if(!doc) {
return NULL;
}
int err =
0;
xmlNode *xml_root = xmlDocGetRootElement(doc);
if(xml_root) {
if(
!xml_root->ns ||
!xstreq(xml_root->name,
"prop") ||
!xstreq(xml_root->ns->href,
"DAV:"))
{
err =
1;
}
}
else {
err =
1;
}
if(err) {
xmlFreeDoc(doc);
return NULL;
}
CxMap *map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
32);
xmlNode *n = xml_root->children;
while(n) {
if(n->type ==
XML_ELEMENT_NODE && n->ns && n->ns->href) {
DavProperty *property = dav_session_malloc(sn,
sizeof(DavProperty));
property->name = dav_session_strdup(sn, (
const char*)n->name);
property->ns = dav_session_malloc(sn,
sizeof(DavNamespace));
property->ns->name = dav_session_strdup(sn, (
const char*)n->ns->href);
property->ns->prefix = n->ns->prefix ?
dav_session_strdup(sn, (
const char*)n->ns->prefix) :
NULL;
property->value = n->children ? dav_convert_xml(sn, n->children) :
NULL;
cxmutstr propkey = dav_property_key(property->ns->name, property->name);
cxMapPut(map, cx_hash_key_cxstr(propkey), property);
cx_strfree(&propkey);
}
n = n->next;
}
xmlFreeDoc(doc);
if(cxMapSize(map) ==
0) {
cxMapDestroy(map);
return NULL;
}
return map;
}
static size_t in_write(
const char *ptr,
size_t size,
size_t nitems,
void *in_stream) {
DavInputStream *in = in_stream;
size_t len = size * nitems;
if(in->alloc < len) {
char *newb = realloc(in->buffer, len);
if(!newb) {
if(in->buffer) free(in->buffer);
in->eof =
1;
return 0;
}
in->buffer = newb;
in->alloc = len;
}
memcpy(in->buffer, ptr, len);
in->size = len;
in->pos =
0;
return nitems;
}