#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cx/buffer.h>
#include <cx/mempool.h>
#include <cx/hash_map.h>
#include "utils.h"
#include "session.h"
#include "resource.h"
#include "methods.h"
DavSession* dav_session_new(DavContext *context,
char *base_url) {
if(!base_url) {
return NULL;
}
cxstring url = cx_str(base_url);
if(url.length ==
0) {
return NULL;
}
DavSession *sn = malloc(
sizeof(DavSession));
memset(sn,
0,
sizeof(DavSession));
sn->mp = cxBasicMempoolCreate(
DAV_SESSION_MEMPOOL_SIZE);
sn->pathcache = cxHashMapCreate(sn->mp->allocator,
CX_STORE_POINTERS,
DAV_PATH_CACHE_SIZE);
sn->key =
NULL;
sn->errorstr =
NULL;
sn->error =
DAV_OK;
sn->flags =
0;
dav_session_set_baseurl(sn, base_url);
sn->handle = curl_easy_init();
curl_easy_setopt(sn->handle,
CURLOPT_FOLLOWLOCATION,
1L);
sn->locks =
NULL;
DavProxy *proxy = cx_strprefix(url,
CX_STR(
"https")) ? context->https_proxy
: context->http_proxy;
if (proxy->url) {
curl_easy_setopt(sn->handle,
CURLOPT_PROXY, proxy->url);
if (proxy->username) {
curl_easy_setopt(sn->handle,
CURLOPT_PROXYUSERNAME,
proxy->username);
if (proxy->password) {
curl_easy_setopt(sn->handle,
CURLOPT_PROXYPASSWORD,
proxy->password);
}
else {
}
}
if(proxy->no_proxy) {
curl_easy_setopt(sn->handle,
CURLOPT_NOPROXY,
proxy->no_proxy);
}
}
#if LIBCURL_VERSION_NUM >= 0x072D00
curl_easy_setopt(sn->handle,
CURLOPT_DEFAULT_PROTOCOL,
"http");
#endif
curl_easy_setopt(sn->handle,
CURLOPT_URL, base_url);
dav_context_add_session(context, sn);
sn->context = context;
return sn;
}
DavSession* dav_session_new_auth(
DavContext *context,
char *base_url,
char *user,
char *password)
{
DavSession *sn = dav_session_new(context, base_url);
if(!sn) {
return NULL;
}
dav_session_set_auth(sn, user, password);
return sn;
}
DavSession* dav_session_clone(DavSession *sn) {
CURL *newhandle = curl_easy_duphandle(sn->handle);
DavSession *newsn = malloc(
sizeof(DavSession));
memset(newsn,
0,
sizeof(DavSession));
newsn->mp = cxBasicMempoolCreate(
DAV_SESSION_MEMPOOL_SIZE);
newsn->pathcache = cxHashMapCreate(newsn->mp->allocator,
CX_STORE_POINTERS,
DAV_PATH_CACHE_SIZE);
newsn->key = sn->key;
newsn->errorstr =
NULL;
newsn->error =
DAV_OK;
newsn->flags =
0;
newsn->handle = newhandle;
newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr;
newsn->auth_prompt = sn->auth_prompt;
newsn->authprompt_userdata = sn->authprompt_userdata;
newsn->logfunc = sn->logfunc;
newsn->get_progress = sn->get_progress;
newsn->put_progress = sn->put_progress;
newsn->progress_userdata = sn->progress_userdata;
dav_context_add_session(sn->context, newsn);
newsn->context = sn->context;
return newsn;
}
void dav_session_set_auth(DavSession *sn,
const char *user,
const char *password) {
if(user && password) {
dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
}
}
void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) {
if(user.length >
0 && password.length >
0) {
size_t upwdlen = user.length + password.length +
2;
char *upwdbuf = malloc(upwdlen);
snprintf(upwdbuf, upwdlen,
"%.*s:%.*s", (
int)user.length, user.ptr, (
int)password.length, password.ptr);
curl_easy_setopt(sn->handle,
CURLOPT_USERPWD, upwdbuf);
free(upwdbuf);
}
}
void dav_session_set_baseurl(DavSession *sn,
char *base_url) {
const CxAllocator *a = sn->mp->allocator;
if(sn->base_url) {
cxFree(a, sn->base_url);
}
cxstring url = cx_str(base_url);
if(url.ptr[url.length -
1] ==
'/') {
cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
sn->base_url = url_m.ptr;
}
else {
char *url_str = cxMalloc(a, url.length +
2);
memcpy(url_str, base_url, url.length);
url_str[url.length] =
'/';
url_str[url.length +
1] =
'\0';
sn->base_url = url_str;
}
}
void dav_session_enable_encryption(DavSession *sn, DavKey *key,
int flags) {
sn->key = key;
if(flags !=
0) {
sn->flags |= flags;
}
else {
sn->flags |=
DAV_SESSION_ENCRYPT_CONTENT;
}
}
void dav_session_set_authcallback(DavSession *sn, dav_auth_func func,
void *userdata) {
sn->auth_prompt = func;
sn->authprompt_userdata = userdata;
}
void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put,
void *userdata) {
sn->get_progress = get;
sn->put_progress = put;
sn->progress_userdata = userdata;
}
CURLcode dav_session_curl_perform(DavSession *sn,
long *status) {
return dav_session_curl_perform_buf(sn,
NULL,
NULL, status);
}
CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response,
long *status) {
CURLcode ret = curl_easy_perform(sn->handle);
long http_status;
curl_easy_getinfo(sn->handle,
CURLINFO_RESPONSE_CODE, &http_status);
if(ret ==
CURLE_OK) {
if(sn->logfunc) {
char *log_method;
char *log_url;
curl_easy_getinfo(sn->handle,
CURLINFO_EFFECTIVE_URL, &log_url);
curl_easy_getinfo(sn->handle,
CURLINFO_EFFECTIVE_METHOD , &log_method);
char *log_reqbody =
NULL;
size_t log_reqbodylen =
0;
char *log_rpbody =
NULL;
size_t log_rpbodylen =
0;
if(request) {
log_reqbody = request->space;
log_reqbodylen = request->size;
}
if(response) {
log_rpbody = response->space;
log_rpbodylen = response->size;
}
sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
}
if(http_status ==
401 && sn->auth_prompt) {
if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
if(request) {
cxBufferSeek(request,
0,
SEEK_SET);
}
if(response) {
cxBufferSeek(response,
0,
SEEK_SET);
}
ret = curl_easy_perform(sn->handle);
curl_easy_getinfo(sn->handle,
CURLINFO_RESPONSE_CODE, &http_status);
}
}
}
if(status) {
*status = http_status;
}
return ret;
}
int dav_session_get_progress(
void *clientp,
curl_off_t dltotal,
curl_off_t dlnow,
curl_off_t ultotal,
curl_off_t ulnow) {
DavResource *res = clientp;
DavSession *sn = res->session;
if(sn->get_progress) {
sn->get_progress(res, (
int64_t)dltotal, (
int64_t)dlnow, sn->progress_userdata);
}
return 0;
}
int dav_session_put_progress(
void *clientp,
curl_off_t dltotal,
curl_off_t dlnow,
curl_off_t ultotal,
curl_off_t ulnow) {
DavResource *res = clientp;
DavSession *sn = res->session;
if(sn->put_progress) {
sn->put_progress(res, (
int64_t)ultotal, (
int64_t)ulnow, sn->progress_userdata);
}
return 0;
}
void dav_session_set_error(DavSession *sn, CURLcode c,
int status) {
if(status >
0) {
switch(status) {
default: {
switch(c) {
default: sn->error =
DAV_ERROR;
}
break;
}
case 401: sn->error =
DAV_UNAUTHORIZED;
break;
case 403: sn->error =
DAV_FORBIDDEN;
break;
case 404: sn->error =
DAV_NOT_FOUND;
break;
case 405: sn->error =
DAV_METHOD_NOT_ALLOWED;
break;
case 407: sn->error =
DAV_PROXY_AUTH_REQUIRED;
break;
case 409: sn->error =
DAV_CONFLICT;
break;
case 412: sn->error =
DAV_PRECONDITION_FAILED;
break;
case 413: sn->error =
DAV_REQUEST_ENTITY_TOO_LARGE;
break;
case 414: sn->error =
DAV_REQUEST_URL_TOO_LONG;
break;
case 423: sn->error =
DAV_LOCKED;
break;
case 511: sn->error =
DAV_NET_AUTH_REQUIRED;
break;
}
}
else {
switch(c) {
case CURLE_UNSUPPORTED_PROTOCOL: sn->error =
DAV_UNSUPPORTED_PROTOCOL;
break;
case CURLE_COULDNT_RESOLVE_PROXY: sn->error =
DAV_COULDNT_RESOLVE_PROXY;
break;
case CURLE_COULDNT_RESOLVE_HOST: sn->error =
DAV_COULDNT_RESOLVE_HOST;
break;
case CURLE_COULDNT_CONNECT: sn->error =
DAV_COULDNT_CONNECT;
break;
case CURLE_OPERATION_TIMEDOUT: sn->error =
DAV_TIMEOUT;
break;
case CURLE_SSL_CONNECT_ERROR:
case CURLE_PEER_FAILED_VERIFICATION:
case CURLE_SSL_ENGINE_NOTFOUND:
case CURLE_SSL_ENGINE_SETFAILED:
case CURLE_SSL_CERTPROBLEM:
case CURLE_SSL_CIPHER:
case CURLE_SSL_CACERT_BADFILE:
case CURLE_SSL_SHUTDOWN_FAILED:
case CURLE_SSL_CRL_BADFILE:
case CURLE_SSL_ISSUER_ERROR: sn->error =
DAV_SSL_ERROR;
break;
default: sn->error =
DAV_ERROR;
break;
}
}
if(c !=
CURLE_OK) {
dav_session_set_errstr(sn, curl_easy_strerror(c));
}
else {
dav_session_set_errstr(sn,
NULL);
}
}
void dav_session_set_errstr(DavSession *sn,
const char *str) {
if(sn->errorstr) {
dav_session_free(sn, sn->errorstr);
}
char *errstr =
NULL;
if(str) {
errstr = dav_session_strdup(sn, str);
}
sn->errorstr = errstr;
}
void dav_session_destroy(DavSession *sn) {
if (dav_context_remove_session(sn->context, sn)) {
fprintf(stderr,
"Error: session not found in ctx->sessions\n");
dav_session_destructor(sn);
}
}
void dav_session_destructor(DavSession *sn) {
cxMempoolDestroy(sn->mp);
curl_easy_cleanup(sn->handle);
free(sn);
}
void* dav_session_malloc(DavSession *sn,
size_t size) {
return cxMalloc(sn->mp->allocator, size);
}
void* dav_session_calloc(DavSession *sn,
size_t nelm,
size_t size) {
return cxCalloc(sn->mp->allocator, nelm, size);
}
void* dav_session_realloc(DavSession *sn,
void *ptr,
size_t size) {
return cxRealloc(sn->mp->allocator, ptr, size);
}
void dav_session_free(DavSession *sn,
void *ptr) {
cxFree(sn->mp->allocator, ptr);
}
char* dav_session_strdup(DavSession *sn,
const char *str) {
return cx_strdup_a(sn->mp->allocator, cx_str((
char*)str)).ptr;
}
char* dav_session_create_plain_href(DavSession *sn,
const char *path) {
if(!
DAV_ENCRYPT_NAME(sn) && !
DAV_DECRYPT_NAME(sn)) {
char *url = util_path_to_url(sn, path);
char *href = dav_session_strdup(sn, util_url_path(url));
free(url);
return href;
}
else {
return NULL;
}
}
char* dav_session_get_href(DavSession *sn,
const char *path) {
if(
DAV_DECRYPT_NAME(sn) ||
DAV_ENCRYPT_NAME(sn)) {
cxstring p = cx_str(path);
CxBuffer href;
CxBuffer pbuf;
cxBufferInit(&href,
NULL,
256, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
cxBufferInit(&pbuf,
NULL,
256, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
int start =
0;
int begin =
0;
char *cp = strdup(path);
while(strlen(cp) >
1) {
char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
if(cached) {
start = strlen(cp);
begin = start;
cxBufferPutString(&href, cached);
break;
}
else {
char *f = cp;
cp = util_parent_path(cp);
free(f);
}
}
free(cp);
if(href.pos ==
0) {
cxBufferPutString(&href, util_url_path(sn->base_url));
}
cxmutstr rp = cx_strdup(cx_strn(path, start));
DavResource *root = dav_resource_new(sn, rp.ptr);
free(rp.ptr);
resource_set_href(root, cx_strn(href.space, href.pos));
CxBuffer *rqbuf = create_basic_propfind_request();
cxstring remaining = cx_strsubs(p, start);
CxStrtokCtx elms = cx_strtok(remaining,
CX_STR(
"/"),
INT_MAX);
DavResource *res = root;
cxBufferPutString(&pbuf, res->path);
cxstring elm;
while(cx_strtok_next(&elms, &elm)) {
if(elm.length >
0) {
DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
if(pbuf.space[pbuf.pos-
1] !=
'/') {
if(href.space[href.pos-
1] !=
'/') {
cxBufferPut(&href,
'/');
}
cxBufferPut(&pbuf,
'/');
}
cxstring pp = cx_strn(pbuf.space, pbuf.size);
cxstring hh = cx_strn(href.space, href.size);
dav_session_cache_path(sn, pp, hh);
cxBufferWrite(elm.ptr,
1, elm.length, &pbuf);
if(child) {
cxBufferPutString(&href, util_resource_name(child->href));
res = child;
}
else if(
DAV_ENCRYPT_NAME(sn)) {
char *random_name = util_random_str();
cxBufferPutString(&href, random_name);
free(random_name);
}
else {
cxstring resname = cx_str(util_resource_name((
const char*)path));
if(resname.ptr[resname.length-
1] ==
'/') {
char *esc = curl_easy_escape(sn->handle,
resname.ptr, resname.length-
1);
cxBufferWrite(esc,
1, strlen(esc), &href);
cxBufferPut(&href,
'/');
curl_free(esc);
}
else {
char *esc = curl_easy_escape(sn->handle,
resname.ptr, resname.length);
cxBufferWrite(esc,
1, strlen(esc), &href);
curl_free(esc);
}
}
}
}
if(p.ptr[p.length-
1] ==
'/') {
if(href.space[href.pos-
1] !=
'/') {
cxBufferPut(&href,
'/');
}
cxBufferPut(&pbuf,
'/');
}
cxstring pp = cx_strn(pbuf.space, pbuf.size);
cxstring hh = cx_strn(href.space, href.size);
dav_session_cache_path(sn, pp, hh);
cxmutstr href_str = cx_strdup_a(
sn->mp->allocator,
cx_strn(href.space, href.size));
dav_resource_free_all(root);
cxBufferFree(rqbuf);
cxBufferDestroy(&pbuf);
cxBufferDestroy(&href);
return href_str.ptr;
}
else {
return dav_session_create_plain_href(sn, path);
}
}
DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf,
const char *name) {
if(res && !dav_propfind(sn, res, rqbuf)) {
DavResource *child = res->children;
while(child) {
if(!strcmp(child->name, name)) {
return child;
}
child = child->next;
}
}
return NULL;
}
void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
CxHashKey path_key = cx_hash_key(path.ptr, path.length);
char *elm = cxMapGet(sn->pathcache, path_key);
if(!elm) {
cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
cxMapPut(sn->pathcache, path_key, href_s.ptr);
}
}
DavLock* dav_create_lock(DavSession *sn,
const char *token,
char *timeout) {
DavLock *lock = dav_session_malloc(sn,
sizeof(DavLock));
lock->path =
NULL;
lock->token = dav_session_strdup(sn, token);
return lock;
}
void dav_destroy_lock(DavSession *sn, DavLock *lock) {
dav_session_free(sn, lock->token);
if(lock->path) {
dav_session_free(sn, lock->path);
}
dav_session_free(sn, lock);
}
static int dav_lock_cmp(
void const *left,
void const *right) {
const DavLock *l = left;
const DavLock *r = right;
return strcmp(l->path, r->path);
}
static int create_lock_manager(DavSession *sn) {
DavLockManager *locks = cxMalloc(sn->mp->allocator,
sizeof(DavLockManager));
locks->resource_locks = cxHashMapCreate(sn->mp->allocator,
CX_STORE_POINTERS,
16);
locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp,
CX_STORE_POINTERS);
sn->locks = locks;
return 0;
}
static DavLockManager* get_lock_manager(DavSession *sn) {
DavLockManager *locks = sn->locks;
if(!locks) {
if(create_lock_manager(sn)) {
return NULL;
}
locks = sn->locks;
}
return locks;
}
int dav_add_resource_lock(DavSession *sn,
const char *path, DavLock *lock) {
DavLockManager *locks = get_lock_manager(sn);
if(!locks) {
return -
1;
}
CxHashKey path_key = cx_hash_key_str(path);
if(cxMapGet(locks->resource_locks, path_key)) {
return -
1;
}
cxMapPut(locks->resource_locks, path_key, lock);
return 0;
}
int dav_add_collection_lock(DavSession *sn,
const char *path, DavLock *lock) {
DavLockManager *locks = get_lock_manager(sn);
if(!locks) {
return -
1;
}
lock->path = dav_session_strdup(sn, path);
cxListAdd(locks->collection_locks, lock);
cxListSort(locks->collection_locks);
return 0;
}
DavLock* dav_get_lock(DavSession *sn,
const char *path) {
DavLockManager *locks = get_lock_manager(sn);
if(!locks) {
return NULL;
}
cxstring p = cx_str(path);
DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
if(lock) {
return lock;
}
CxIterator i = cxListIterator(locks->collection_locks);
cx_foreach(DavLock*, col_lock, i) {
int cmd = strcmp(path, col_lock->path);
if(cmd ==
0) {
return col_lock;
}
else if(cx_strprefix(p, cx_str(col_lock->path))) {
return col_lock;
}
else if(cmd >
0) {
break;
}
}
return NULL;
}
void dav_remove_lock(DavSession *sn,
const char *path, DavLock *lock) {
DavLockManager *locks = get_lock_manager(sn);
if(!locks) {
return;
}
if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
return;
}
cxListFindRemove(locks->collection_locks, lock);
}